When I first learned the Promise of ES6, I wrote a blog post titled “Comparison between Promise and CO with generator function to Solve ASYNCHRONOUS FLOW of JS code”, in which I compared the similarities and differences of using Promise and CO module with generator function to solve ASYNCHRONOUS JS.
At the end of the article, the async and await of ES7 are mentioned, but it was only briefly mentioned at that time without in-depth discussion.
Support for async and await has been added to Nodejs V7 in the last two months, so today we’ll take a closer look at this. Write asynchronous code in a more elegant way.
What is async/await
Async /await can be said to be the syntactic sugar of CO modules and generator functions. Solve ASYNCHRONOUS JS code with clearer semantics.
Those familiar with CO module should know that CO module is a module written by TJ that uses generator functions to solve asynchronous processes. It can be regarded as the executor of generator functions. While async/await is an upgrade to CO module, an executor of built-in generator functions that no longer depends on CO module. At the same time async returns promises.
From the above, both CO module and async/await take Promise as the most basic unit. Students who do not know much about Promise can have an in-depth understanding of Promise first.
The Promise of co, async/await
Let’s use a simple example to compare the similarities and differences between the three approaches, as well as the trade-offs.
We use the nodeJS driver of mongodb to query the mongodb database as an example. The reason is that the jS driver of mongodb has already implemented the return Promise by default, instead of us wrapping the Promise separately.
Use the chain of Promise
MongoClient.connect(url + db_name).then(db => {
return db.collection('blogs');
}).then(coll => {
return coll.find().toArray();
}).then(blogs => {
console.log(blogs.length);
}).catch(err => {
console.log(err);
})Copy the code
A Promise’s then() method can return another Promise or a synchronized value, and if it returns a synchronized value, it will be wrapped as a Promise. In the example above, db.collection() will return a synchronized value, the collection object, but wrapped as a Promise that will be passed transparently to the next THEN () method. In the example above, the Promise chain is used. Mongoclient.connect () returns a Promise, then get the db object in the then() method, then get the COLl object and return. The next THEN () method retrieves the COLL object, queries it, returns the results, and calls the THEN () method layer by layer, forming a chain of promises. In this Promise chain, if any link fails, it will be caught by a final catch(). This code, written using the Promise chain, is arguably more elegant and unambiguous than the layers of callbacks. The database object is obtained, the collection object is obtained, and the data is queried. But there is a less “elegant” problem here. Each THEN () method retrieves an object that was returned by the previous THEN () method. It cannot be accessed across layers. Blogs => {}, blogs => {}, blogs => {}, blogs => {}, blogs => {} What if you want to close the db.close() database after printing out the blogs list? At this time, there are two solutions:
The first is to use then() nesting. We break the Promise chain and make it nested as if we were using a callback function:
MongoClient.connect(url + db_name).then(db => {
let coll = db.collection('blogs');
coll.find().toArray().then(blogs => {
console.log(blogs.length);
db.close();
}).catch(err => {
console.log(err);
});
}).catch(err => {
console.log(err);
})
Copy the code
Here we nested the two promises so that in the last query operation, the outside DB object could be called. But this is not recommended. The reason is simple: we’ve gone from one kind of callback hell to another kind of Promise callback hell. Also, we need to catch exceptions for each Promise because the Promise is not chained.
Another way is to pass db in each then() method:
MongoClient.connect(url + db_name).then(db => { return {db:db,coll:db.collection('blogs')}; }).then(result => { return {db:result.db,blogs:result.coll.find().toArray()}; }).then(result => {return result.blogs. Then (blogs => {result.coll.find().toarray ()) Return {db:result.db,blogs:blogs}})}). Then (result => {console.log(result.blogs. Length); result.db.close(); }).catch(err => { console.log(err); });Copy the code
We return db and its other results as an object in each then() method. Note that it is fine if the result is a synchronous value each time, but if it is a Promise value, each Promise needs to do an extra layer of parsing. Such as an example of the above, then the second () method returns the {db: result. Db, blogs: result. Coll. The find (). The toArray ()} object, blogs is a Promise, the next then () method, We cannot reference the blog list array values directly, so we need to call the then() method first to parse a layer and then return the two synchronized values db and blogs. Note that nesting of promises is involved here, but a Promise only nested one layer of THEN ().
This approach is also a pain in the neck, because if the then() method returns a Promise instead of a synchronous value, we need to do a lot more work. Also, passing through one “redundant” DB object at a time is logically redundant.
But in addition, for the use of Promise chains, if you encounter the above problems, there seems to be no other better way to solve. We can only choose an “optimal” solution based on the scenario, if the Promise chain is to be used.
Given the Promise problem, TJ wrapped the ES6 generator functions in a CO module to solve the problem in a more elegant way.
Co is paired with generator functions
If you use the CO module with the generator function, the above example can be rewritten as follows:
const co = require('co');
co(function* (){
let db = yield MongoClient.connect(url + db_name);
let coll = db.collection('blogs');
let blogs = yield coll.find().toArray();
console.log(blogs.length);
db.close();
}).catch(err => {
console.log(err);
});Copy the code
Co is a function that takes a generator function as an argument to execute the generator function. The yield keyword is used in generator functions to “synchronously” get the value of each asynchronous operation. The code above is more elegant in code form than using the Promise chain above. We’ve eliminated the callback function, and the code looks synchronized. Except that using CO and yield is a little weird.
With the CO module, we wrap all operations into a generator function, and then call the generator function with co(). It seems acceptable, but the EVOLUTION of ES is not satisfied with this, so async/await has been mentioned in the ES7 proposal.
async/await
Let’s take a look at rewriting the above code with async/await:
(async function(){
let db = await MongoClient.connect(url + db_name);
let coll = db.collection('blogs');
let blogs = await coll.find().toArray();
console.log(blogs.length);
db.close();
})().catch(err => {
console.log(err);
});Copy the code
Comparing the code, we can see that the code of async/await and CO are very similar. Co is replaced with async and yield with await. At the same time, the generator function becomes a normal function. This is more semantically clear, async means that the function is asynchronous and await means to “wait” for the return value of the asynchronous operation.
The async function returns a Promise, which looks like this:
let getBlogs = async function(){
let db = await MongoClient.connect(url + db_name);
let coll = db.collection('blogs');
let blogs = await coll.find().toArray();
db.close();
return blogs;
};
getBlogs().then(result => {
console.log(result.length);
}).catch(err => {
console.log(err);
})Copy the code
We define getBlogs as an async function, and the resulting list of blogs will eventually be returned as a Promise, as above, we call getBlogs().then() directly to get the value returned by the async function.
Ok, above we have a brief comparison of the three async/await solutions. Let’s take a closer look at async/await solutions.
In-depth async/await
Async return value
Async is used to define an asynchronous function that returns a Promise. If async returns a synchronized value, the value is wrapped as a Promise to resolve, equivalent to return promise.resolve (value). Await is used before an asynchronous operation to “wait” for the return value of the asynchronous operation. Await can also be used with a synchronized value.
// Return a Promise let timer = async function timer(){return new Promise((resolve,reject) => {setTimeout(() => { resolve('500'); }, 500); }); } timer().then(result => { console.log(result); //500 }).catch(err => { console.log(err.message); });Copy the code
SayHi = async function sayHi(){let hi = await 'hello world'; return hi; // equivalent to return promise.resolve (hi); } sayHi().then(result => { console.log(result); });Copy the code
The above example returns a synchronous value. The string ‘Hello world’, sayHi() is an async function, and the return value is wrapped as a Promise. Then () can be called to retrieve the return value. For a synchronized value, you can use await or not await. The effect is the same. It depends.
For example, let coll = db.collection(‘blogs’); We are not using await here because this is a synchronous value. Of course, you can also use await so that the code looks uniform. Although the effect is the same.
Exception of async function
Let sayHi = async function sayHi(){throw new Error(' Error '); } sayHi().then(result => { console.log(result); }).catch(err => { console.log(err.message); // error});Copy the code
We throw an exception directly in the async function. Since it returns a Promise, this exception can be caught by calling the catch() method that returns a Promise.
Contrast with the Promise chain: Our async function can contain multiple asynchronous operations that have exceptions in common with the Promise chain. If a Promise is rejected (), none of the subsequent operations will be performed.
Let count = ()=>{return new Promise((resolve,reject) =>{setTimeout(()=>{reject(' intentionally throw error ')); }, 500); }); } let list = ()=>{return new Promise(resolve,reject)=>{setTimeout(()=>{resolve([1,2,3])); }, 500); }); } let getList = async ()=>{ let c = await count(); let l = await list(); return {count:c,list:l}; } console.time('begin'); getList().then(result => { console.log(result); }).catch(err => { console.timeEnd('begin'); console.log(err); }); //begin: 507.490ms // An error is intentionally thrownCopy the code
In the code above, we define two asynchronous operations, count and list, and use setTimeout to delay for 500 milliseconds. Count deliberately throws an exception directly. From the output result, count() throws an exception directly, and catch() catches it.
parallel
With async, our examples above are all serial. Like the list() and count() examples above, we can use this example as a scenario for paging through data. Query the total number of records in the database, then query the paging data according to the paging conditions, and finally return the paging data and paging information.
Our above examples count() and list() have a “order”, that is, the total number we looked up first, and then the list. In fact, these two operations are not sequential, we can asynchronously query at the same time, and then wait until all the results are returned to assemble the data.
let count = ()=>{ return new Promise((resolve,reject) => { setTimeout(()=>{ resolve(100); }, 500); }); } let list = ()=>{return new Promise(resolve,reject)=>{setTimeout(()=>{resolve([1,2,3])); }, 500); }); } let getList = async ()=>{ let result = await Promise.all([count(),list()]); return result; } console.time('begin'); getList().then(result => { console.timeEnd('begin'); / / the begin: 505.557 ms console. The log (result); //[ 100, [ 1, 2, 3 ] ] }).catch(err => { console.timeEnd('begin'); console.log(err); });Copy the code
We execute count() and list() “simultaneously” using promise.all (), where count() and list() can be considered “parallel” and will take the longest time of the two asynchronous operations. The final result is an array of the results of the two operations. We just pull the values out of the array in order.