From Node.js7.6, Node.js is equipped with V8 engine with async function. When Node.js 8 became the LTS version on October 31, there was no reason not to use the async function. Next, I will briefly introduce the async function and how to change the way we write the Node.js application.
What is async function
The async function allows you to write promise based code to make it look synchronized. Whenever you define a function body with the asnyc keyword, you can use the await keyword in the function body. When the async function is called, you will get a promise instance. This promise is executed when the async function returns a value. If the async function throws an error, it will enter the rejected process of project.
The await keyword can be used to wait for Promise to enter resolved with a completion return value. If the value passed to await is not a Promise instance, it will be converted to Promise's resolved process.
const rp = require('request-promise'); async function main () { const result = await rp('https://google.com'); const twenty = await 20; // sleeeeeeeeping for a second await new Promise (resolve => { setTimeout(resolve, 1000); }); return result } main() .then(console.log) .catch(console.error);
Migrate to async function
If your Node.js application already uses promise, you only need to use await instead of promise chain call. If your code is based on callback, migrating to async functions requires gradual modification of existing code. You can use the new technology in the new to function, and you can use promise for simple packaging if you have to keep the old function. To do this you can use the built-in util. Promise method!
const util = require('util'); const {readFile} = require('fs'); const readFileAsync = util.promisify(readFile); async function main () { const result = await readFileAsync('.gitignore'); return result } main() .then(console.log) .catch(console.error);
async function best practices
Using async function in express
As express supports Promises out of the box, using async functions with express is as simple as:
express supports Promise, so using async function can simplify the code as follows:
const express = require('express'); const app = express(); app.get('/', async (request, response) => { // awaiting Promises here // if you just await a single promise, you could simply return with it, // no need to await for it const result = await getContent(); response.send(result); }); app.listen(process.env.PORT);
Edit1: as Keith Smith pointed out, the above example has a serious problem - if project enters rejected, the express routing handler will hang because there is no error handling.
To solve this problem, you should encapsulate your asynchronous handler in a function that handles errors:
const awaitHandlerFactory = middleware => { return async (req, res, next) => { try { await middleware(req, res, next) } catch (err) { next(err) } } } // and use it this way: app.get('/', awaitHandlerFactory(async (request, response) => { const result = await getContent(); response.send(result); }));
parallel
Suppose you are doing something similar, when an operation requires two inputs, one from the database and the other from an external service:
async function main () { const user = await Users.fetch(userId); const product = await Products.fetch(productId); await makePurchase(user, product); }
In this case, the following will happen:
- Your code will get user resources first.
- Then get the product resources.
- And finally purchase.
As you can see, you can do the first two operations at the same time, because there is no dependency between them. To do this, use the Promise.all method:
async function main () { const [user, product] = await Promise.all([ Users.fetch(userId), Products.fetch(productId) ]); await makePurchase(user, product); }
In some cases, you just need to resolve as quickly as possible to get Promise results - in this case, you can use the Promise.race method.
error handling
Refer to the following code
async function main () { await new Promise((resolve, reject) => { reject(new Error('💥')); }); }; main() .then(console.log);
If you run this code, you will see a similar message on terminal:
(node:69738) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: 💥 (node:69738) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
In the new version of Node.js, if Promise rejection is not handled, the entire Node.js process will crash. Therefore, try catch statements should be used when necessary:
const util = require('util'); async function main () { try { await new Promise((resolve, reject) => { reject(new Error(' ')); }); } catch (err) { // handle error case // maybe throwing is okay depending on your use-case } } main() .then(console.log) .catch(console.error);
However, if you use try catch to lose important exceptions such as system errors, you need to re throw the exception. To learn more about when to throw again, I strongly recommend reading Eran's Learning to Throw Again..
Complex control process
The first asynchronous control flow Library of Node.js was written by Cao LAN McMahon. async The asynchronous control flow library for. It provides multiple asynchronous assistants:
- mapLimit,
- filterLimit,
- concatLimit,
- priorityQueue.
If you don't want to rebuild the wheel, and you don't want to use this library, you can heavily use the async function and util. Promise method:
const util = require('util'); const async = require('async'); const numbers = [ 1, 2, 3, 4, 5 ]; mapLimitAsync = util.promisify(async.mapLimit); async function main () { return await mapLimitAsync(numbers, 2, (number, done) => { setTimeout(function () { done(null, number * 2); }, 100) }); }; main() .then(console.log) .catch(console.error);
- My blog: neverland.github.io
- My email enix@foxmail.com