In a recent review of asynchronous programming in JS, I looked back at Promises and found some important points I missed, such as what does promise.resolve() mean when passing different arguments? Like what happens when one promise relies on another promise? For example, when a catch catches an error, will the subsequent then methods continue? Here are some answers to these questions and highlight some important points.
1. Promise syntax
The central idea of Promise programming is that if the data is promised, then do something.
Here is an example of a Promise.
const promise = new Promise(function(resolve, reject) {
// ... some code
ifResolve (value); }else{ reject(error); }});Copy the code
The Promise constructor takes a function as an argument, resolve and reject.
The resolve function changes the state of the Promise object from “unfinished” to “successful.” It will be called when the asynchronous operation succeeds and will pass the result of the asynchronous operation as an argument.
The Reject function changes the state of the Promise object from “unfinished” to “failed” (i.e., from Pending to Rejected). It is called when the asynchronous operation fails and passes the error reported by the asynchronous operation as a parameter.
After the Promise instance is generated, you can use the THEN method to specify the resolved and Rejected state callback functions, respectively.
promise.then(function(value) {
// success
}, function(error) {
// failure
});
Copy the code
The then method can take two callback functions as arguments.
The first callback is called when the Promise object’s state changes to Resolved. The second callback is called when the Promise object’s state changes to Rejected. The second function is optional and does not have to be provided. Both of these functions accept as arguments a value passed from the Promise object.
Here is an example of using then. The then method returns a new Promise instance. So you can write it chained, where a then method is followed by another THEN method.
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
Copy the code
The code above specifies two callback functions in turn, using the THEN method. After the first callback completes, the second callback is passed the result as an argument.
The promise.prototype. catch method is used to specify the callback function when an error occurs.
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {// Handle getJSON and an error that occurred while the previous callback function was running console.log('Error! ', error);
});
Copy the code
In the code above, the getJSON method returns a Promise object; If an asynchronous operation throws an error, the status changes to Rejected, and the callback specified by the catch method is called to handle the error. In addition, the then method specifies a callback that is caught by the catch method if it throws an error during execution.
It is always recommended that a catch method be followed by a Promise object to handle errors that occur within a Promise. The catch method still returns a Promise object, so you can call the then method later.
Note:
1. If resolve and reject are called with arguments, their arguments are passed to the callback. The argument to the resolve function may be another Promise instance in addition to the normal value.
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
Copy the code
In the code above, p1 and p2 are both instances of Promise, but P2’s resolve method takes P1 as an argument, meaning that the result of an asynchronous operation is the return of another asynchronous operation.
So p1’s state is going to be transferred to P2, which means that p1’s state determines P2’s state. If P1’s state is pending, p2’s callback waits for p1’s state to change. If P1 is in resolved or Rejected, the P2 callback will be executed immediately.
2. Calling resolve or Reject does not terminate the execution of the Promise argument function.
3. The then method is defined on the prototype object Promise.prototype.
4. If there are no callbacks specifying error handling using the catch method, the errors thrown by the Promise object are not passed to the outer code, that is, there is no response.
5. If a Promise is raised after a resolve statement, the error will not be caught. Because once a Promise state changes, it stays that state forever.
2. Promise.resolve()
Sometimes you need to turn an existing object into a Promise object, and the promise.resolve method does this.
Promise. Resolve is equivalent to the following
Promise.resolve('foo'New Promise(resolve => resolve();'foo'))
Copy the code
The parameters of the promise.resolve method are divided into four cases.
1) The argument is an instance of a Promise
If the argument is a Promise instance, promise.resolve will return the instance unchanged.
2) The argument is a Thenable object
Thenable objects refer to objects that have then methods, such as this one.
let thenable = {
then: function(resolve, reject) { resolve(42); }};Copy the code
The promise. resolve method converts this object to a Promise, and then immediately executes the thenable object’s then method.
let thenable = {
then: function(resolve, reject) { resolve(42); }};let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
Copy the code
As soon as the thenable object’s THEN method executes, object P1 will be in the resolved state and the callback specified by the last THEN method will execute immediately, printing 42.
3) Arguments are not objects with then methods, or are not objects at all.
If the parameter is a raw value, or an object that does not have a then method, the promise.resolve method returns a new Promise object with the state Resolved.
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
Copy the code
The code above generates a new instance P of the Promise object. Since the string Hello is not an asynchronous operation (the string object does not have the then method), the return Promise instance state from lifetime achievement is Resolved, so the callback will execute immediately. The arguments to the promise. resolve method are also passed to the callback function.
4) No parameters
The Promise. Resolve method allows you to call an Resolved Promise object with no arguments.
So, if you want to get a Promise object, it’s convenient to call the promise.resolve method directly.
const p = Promise.resolve();
p.then(function() {/ /... });Copy the code
The variable P in the code above is a Promise object. Note that the Promise object for Resolve now is at the end of this event loop, not at the beginning of the next.
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
Copy the code
In the code above, setTimeout(fn, 0) is executed at the start of the next event loop, promise.resolve () is executed at the end of the current event loop, and console.log(‘one’) is executed immediately, so it is printed first.
3. Novice error
1) How to use forEach after Promises?
// I want to delete allDocs db.alldocs ({include_docs:true}).then(function (result) {
result.rows.forEach(function (row) {
db.remove(row.doc);
});
}).then(function() {// I naively thought all docs were deleted! });Copy the code
The problem is that the first function actually returns undefined, which means the second method doesn’t wait for all Documents to execute db.remove(). He doesn’t actually wait for anything, and may execute after any number of documents have been deleted!
In short, forEach()/for/while is not the solution you are looking for. All you need is promise.all ():
db.allDocs({include_docs: true}).then(function (result) {
return Promise.all(result.rows.map(function (row) {
return db.remove(row.doc);
}));
}).then(function (arrayOfResults) {
// All docs have really been removed() now!
});
Copy the code
What does the above code mean? Basically, promise.all () will take an promises array as input and return a new Promise. This new promise will not return until all promises in the array have been successfully returned. It’s the asynchronous version of the for loop.
And promise.all () returns an array of execution results to the next function, useful when you want to retrieve multiple objects from PouchDB, for example. An even more useful effect is that promise.all () will return an error if any of the promises in the array return an error.
2) Forget to use catch
Many developers forget to add a.catch() to their code simply because they believe that promises will never go wrong. Unfortunately, this also means that any exceptions thrown will be eaten and you won’t be able to see them on console. This kind of problem can be very painful to debug.
3) Use side effects to call instead of return
What’s wrong with the following code?
somePromise().then(function () {
someOtherPromise();
}).then(function () {
// 我希望someOtherPromise() 状态变成resolved!
// 但是并没有
});
Copy the code
Each promise will provide you with a then() function (or catch(), which is really just then(null…). Grammar sugar). When we are inside the then() function:
somePromise().then(function () {
// I'm inside a then() function!
});
Copy the code
What can we do? There are three things:
- Return another promise
- Return a synchronized value (or undefined)
- Throw a synchronization exception
That’s it. Once you understand this technique, you understand Promises.
So let’s take them one by one.
Return another promise
getUserByName('nolan').then(function (user) {
return getUserAccountById(user.id);
}).then(function (userAccount) {
// I got a user account!
});
Copy the code
Note: I’m the second return promise, and this return is very important. If I hadn’t written return, getUserAccountById() would have been a side effect, and the next function would have received undefined instead of userAccount.
Return a synchronization value (or undefined)
Returning undefined is usually wrong, but returning a sync value is actually a great way to wrap sync code in promise-style code. For example, we have an in-memory cache for the Users information. We can do this:
getUserByName('nolan').then(function (user) {
if (inMemoryCache[user.id]) {
return inMemoryCache[user.id]; // returning a synchronous value!
}
return getUserAccountById(user.id); // returning a promise!
}).then(function (userAccount) {
// I got a user account!
});
Copy the code
The second function does not care whether the userAccount is taken from a synchronous or asynchronous method, and the first function is free to return a synchronous or asynchronous value.
Throw a synchronization exception
For example, we want to throw a synchronization exception when the user has logged out. This will be very simple:
getUserByName('nolan').then(function (user) {
if (user.isLoggedOut()) {
throw new Error('user logged out! '); // throwing a synchronous error!
}
if (inMemoryCache[user.id]) {
return inMemoryCache[user.id]; // returning a synchronous value!
}
return getUserAccountById(user.id); // returning a promise!
}).then(function (userAccount) {
// I got a user account!
}).catch(function (err) {
// Boo, I got an error!
});
Copy the code
Our catch() will receive a synchronous exception if the user has logged out, and will also receive an asynchronous exception in subsequent promises. Again, this function does not care whether the exception is returned synchronously or asynchronously.
4. Refer to web sites
http://fex.baidu.com/blog/2015/07/we-have-a-problem-with-promises/
http://es6.ruanyifeng.com/#docs/promise
See my blog for the original article