Promise
Overview
Promise object is JavaScript's asynchronous operation solution, which provides a unified interface for asynchronous operation. It acts as a proxy, acting as an intermediary between asynchronous operations and callback functions, so that asynchronous operations have a synchronous operation interface. Promise allows asynchronous operations to be written, just like in the process of writing synchronous operations, without having to nest callback functions layer by layer.
Note that this chapter is just a brief introduction to Promise objects. In order to avoid repetition with subsequent tutorials, for a more complete introduction, please see "Promise Object" of "ES6 Standard Introduction" /#docs/promise) chapter.
First of all, Promise is an object and also a constructor.
function f1(resolve, reject) {
// Asynchronous code...
}
var p1 = new Promise(f1);
In the above code, the Promise
constructor accepts a callback function f1
as a parameter, and inside the f1
is the asynchronous operation code. Then, the returned p1
is a Promise instance.
The design idea of Promise is that all asynchronous tasks return a Promise instance. The Promise instance has a then
method to specify the callback function for the next step.
var p1 = new Promise(f1);
p1.then(f2);
In the above code, when the asynchronous operation of f1
is completed, f2
will be executed.
The traditional way of writing may need to pass f2
as a callback function to f1
, such as f1(f2)
. After the asynchronous operation is completed, f2
is called inside f1
. Promise makes f1
and f2
become chain writing. Not only improves readability, but it is especially convenient for multi-level nested callback functions.
// Traditional writing
step1(function (value1) {
step2(value1, function (value2) {
step3(value2, function (value3) {
step4(value3, function (value4) {
// ...
});
});
});
});
// How to write Promise
new Promise(step1).then(step2).then(step3).then(step4);
As you can see from the above code, after adopting Promises, the program flow becomes very clear and easy to read. Note that in order to facilitate understanding, the generation format of the Promise
instance of the above code has been simplified. Please refer to the following for the real syntax.
In general, the traditional way of writing callback functions makes the code mixed together and develops horizontally rather than downwards. Promise is to solve this problem, so that asynchronous processes can be written as synchronous processes.
Promise was originally an idea put forward by the community, and some function libraries were the first to implement this function. ECMAScript 6 writes it into the language standard, and currently JavaScript natively supports Promise objects.
The state of the Promise object
The Promise object controls asynchronous operations through its own state. Promise instances have three states.
-Asynchronous operation is not completed (pending) -Asynchronous operation is successful (fulfilled) -Asynchronous operation failed (rejected)
In the above three states, fulfilled
and rejected
together are called resolved
(finalized).
There are only two ways to change these three states.
-From "incomplete" to "success" -From "incomplete" to "failed"
Once the state changes, it freezes, and there will be no new state changes. This is also the origin of the name Promise, which means "promise" in English. Once the promise is made, it cannot be changed. This also means that the state change of the Promise instance can only happen once.
Therefore, there are only two final results of Promise.
-The asynchronous operation is successful, the Promise instance returns a value, and the status becomes fulfilled
.
-The asynchronous operation fails, the Promise instance throws an error, and the status becomes rejected
.
Promise Constructor
JavaScript provides a native Promise
constructor to generate Promise instances.
var promise = new Promise(function (resolve, reject) {
// ...
if (/* Asynchronous operation succeeded*/){
resolve(value);
} else {/* Asynchronous operation failed*/
reject(new Error());
}
});
In the above code, the Promise
constructor accepts a function as a parameter, and the two parameters of the function are resolve
and reject
. They are two functions, provided by the JavaScript engine, so you don't need to implement them yourself.
The role of the resolve
function is to change the status of the Promise
instance from "unfinished" to "successful" (that is, from pending
to fulfilled
). It is called when the asynchronous operation succeeds, and the asynchronous operation As a result, it is passed as a parameter. The role of the reject
function is to change the status of the Promise
instance from "unfinished" to "failed" (that is, from pending
to rejected
), call it when an asynchronous operation fails, and report the asynchronous operation The error is passed as a parameter.
Below is an example.
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, "done");
});
}
timeout(100);
In the above code, timeout(100)
returns a Promise instance. After 100 milliseconds, the status of the instance will change to fulfilled
.
Promise.prototype.then()
The then
method of the Promise instance is used to add a callback function.
The then
method can accept two callback functions. The first is the callback function when the asynchronous operation succeeds (becomes in the fulfilled
state), and the second is the callback function when the asynchronous operation fails (becomes rejected
). This parameter can be omitted). Once the state changes, call the corresponding callback function.
var p1 = new Promise(function (resolve, reject) {
resolve("success");
});
p1.then(console.log, console.error);
// "Success"
var p2 = new Promise(function (resolve, reject) {
reject(new Error("Failed"));
});
p2.then(console.log, console.error);
// Error: failed
In the above code, p1
and p2
are both Promise instances, and their then
method binds two callback functions: the callback function console.log
for success, and the callback function console.error
for failure. (Can be omitted). The status of p1
becomes successful, and the status of p2
becomes failed. The corresponding callback function will receive the value returned by the asynchronous operation, and then output it on the console.
The then
method can be used in a chain.
p1.then(step1).then(step2).then(step3).then(console.log, console.error);
In the above code, there are four then
after p1
, which means that there are four callback functions in sequence. As long as the status of the previous step becomes fulfilled
, the callback functions that follow immediately will be executed in turn.
The last then
method, the callback function is console.log
and console.error
, there is an important difference in usage. console.log
only displays the return value of step3
, while console.error
can display errors that occur in any of p1
, step1
, step2
, and step3
. For example, if the status of step1
becomes rejected
, then both step2
and step3
will not be executed (because they are callback functions of resolved
). Promise starts looking, and then the first callback function called rejected
, in the above code is console.error
. This means that the error reporting of the Promise object is transitive.
then() usage analysis
The usage of Promise is simply one sentence: use the then
method to add a callback function. However, different writing methods have some subtle differences. Please see the following four writing methods. What is the difference between them?
// Writing method one
f1().then(function () {
return f2();
});
// Writing method two
f1().then(function () {
f2();
});
// Writing method three
f1().then(f2());
// Writing four
f1().then(f2);
In order to facilitate the explanation, the following four ways of writing all use the then
method to connect to a callback function f3
. The parameter of the f3
callback function written in method one is the result of the operation of the f2
function.
f1()
.then(function () {
return f2();
})
.then(f3);
The parameter of the f3
callback function in the second method is undefined
.
f1()
.then(function () {
f2();
return;
})
.then(f3);
The parameter of the f3
callback function in the third method is the running result of the function returned by the f2
function.
f1().then(f2()).then(f3);
There is only one difference between writing four and writing one, that is, f2
will receive the result returned by f1()
.
f1().then(f2).then(f3);
Example: Image loading
The following is the use of Promise to complete the loading of the picture.
var preloadImage = function (path) {
return new Promise(function (resolve, reject) {
var image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
In the above code, image
is an instance of an image object. It has two event monitoring properties, the onload
property is called after the image is loaded successfully, and the onerror
property is called when the loading fails.
The usage of the above preloadImage()
function is as follows.
preloadImage("https://example.com/my.jpg")
.then(function (e) {
document.body.append(e.target);
})
.then(function () {
console.log("Loaded successfully");
});
In the above code, after the image is loaded successfully, the onload
property will return an event object, so the callback function of the first then()
method will receive this event object. The target
property of this object is the DOM node generated after the image is loaded.
Summary
The advantage of Promise is that the callback function becomes a standardized chain writing method, and the program flow can be seen clearly. It has a complete set of interfaces, which can implement many powerful functions, such as executing multiple asynchronous operations at the same time, and then executing a callback function after their states have changed; another example is to specify uniformly for errors thrown in multiple callback functions Processing methods and so on.
Moreover, Promise has another advantage that traditional writing does not have: once its state changes, it can be obtained whenever it is queried. This means that whenever you add a callback function to a Promise instance, the function will execute correctly. So, you don’t have to worry about whether you missed an event or signal. If it is the traditional way of writing, the callback function is executed by listening to the event. Once the event is missed, adding a callback function will not be executed.
The disadvantage of Promise is that it is more difficult to write than traditional writing, and reading the code is not easy to understand at a glance. You will only see a bunch of then
, you have to sort out the logic in the callback function of then
yourself.
Micro task
The callback function of Promise is an asynchronous task and will be executed after the synchronous task.
new Promise(function (resolve, reject) {
resolve(1);
}).then(console.log);
console.log(2);
// 2
// 1
The above code will output 2 first, and then output 1. Because console.log(2)
is a synchronous task, and the callback function of then
is an asynchronous task and must be executed later than the synchronous task.
However, the callback function of Promise is not a normal asynchronous task, but a microtask. The difference between them is that normal tasks are added to the next round of event loop, and micro tasks are added to this round of event loop. This means that the execution time of the micro task must be earlier than the normal task.
setTimeout(function () {
console.log(1);
}, 0);
new Promise(function (resolve, reject) {
resolve(2);
}).then(console.log);
console.log(3);
// 3
// 2
// 1
The output of the above code is 321
. This shows that the execution time of the callback function of then
is earlier than setTimeout(fn, 0)
. Because then
is executed in the current round of event loop, setTimeout(fn, 0)
is executed at the beginning of the next round of event loop.
Reference link
- Sebastian Porto, Asynchronous JS: Callbacks, Listeners, Control Flow Libs and Promises
- Rhys Brett-Bowen, Promises/A+-understanding the spec through implementation
- Matt Podwysocki, Amanda Silver, [Asynchronous Programming in JavaScript with “Promises”](http://blogs.msdn.com/b/ie/archive/2011/09/11/asynchronous-programming-in-javascript-with- promises.aspx)
- Marc Harter, Promise A+ Implementation
- Bryan Klimt, What's so great about JavaScript Promises?
- Jake Archibald, JavaScript Promises There and back again
- Mikito Takada, 7. Control flow, Mixu's Node book