Home >Web Front-end >JS Tutorial >Async and Await function analysis in Node.js
This article mainly introduces you to the relevant knowledge of Async and Await functions in Node.js. You will learn how to use the async function (async/await) in Node.js to simplify callback or Promise. Very good, with reference For reference value, friends who need it can refer to it. I hope it can help everyone.
Asynchronous language structures already exist in other languages, such as c#’s async/await, Kotlin’s coroutines, and go’s goroutines. With the release of Node.js 8, the long-awaited async function is also among them. Implemented by default.
What is the async function in Node?
When a function is declared as an Async function it returns an AsyncFunction object. They are similar to Generators in that execution can be paused. The only difference is that they return a Promise instead of a { value: any, done: Boolean } object. They are still very similar though, and you can use the co package to get the same functionality.
In an async function, you can wait for the Promise to complete or capture the reason for its rejection.
If you want to implement some of your own logic in Promise
function handler (req, res) { return request('https://user-handler-service') .catch((err) => { logger.error('Http error', err) error.logged = true throw err }) .then((response) => Mongo.findOne({ user: response.body.user })) .catch((err) => { !error.logged && logger.error('Mongo error', err) error.logged = true throw err }) .then((document) => executeLogic(req, res, document)) .catch((err) => { !error.logged && console.error(err) res.status(500).send() }) }
You can use async/await to make this code look like synchronously executed code
async function handler (req, res) { let response try { response = await request('https://user-handler-service') } catch (err) { logger.error('Http error', err) return res.status(500).send() } let document try { document = await Mongo.findOne({ user: response.body.user }) } catch (err) { logger.error('Mongo error', err) return res.status(500).send() } executeLogic(document, req, res) }
In the old In the v8 version, if there is a promise rejection that is not handled, you will get a warning and you do not need to create a rejection error listening function. However, it is recommended to exit your application in this case. Because when you don't handle errors, the application is in an unknown state.
process.on('unhandledRejection', (err) => { console.error(err) process.exit(1) })
async function pattern
When dealing with asynchronous operations, there are many examples of making them look like synchronous code. If you use Promise or callbacks to solve the problem, you need to use a very complex pattern or external library.
It is a very complicated situation when you need to use asynchronous acquisition of data in a loop or use if-else conditions.
Exponential rollback mechanism
Using Promise to implement rollback logic is quite clumsy
function requestWithRetry (url, retryCount) { if (retryCount) { return new Promise((resolve, reject) => { const timeout = Math.pow(2, retryCount) setTimeout(() => { console.log('Waiting', timeout, 'ms') _requestWithRetry(url, retryCount) .then(resolve) .catch(reject) }, timeout) }) } else { return _requestWithRetry(url, 0) } } function _requestWithRetry (url, retryCount) { return request(url, retryCount) .catch((err) => { if (err.statusCode && err.statusCode >= 500) { console.log('Retrying', err.message, retryCount) return requestWithRetry(url, ++retryCount) } throw err }) } requestWithRetry('http://localhost:3000') .then((res) => { console.log(res) }) .catch(err => { console.error(err) })
The code is very troublesome to look at, and you don’t want to see such code. We can redo this example using async/await to make it simpler
function wait (timeout) { return new Promise((resolve) => { setTimeout(() => { resolve() }, timeout) }) } async function requestWithRetry (url) { const MAX_RETRIES = 10 for (let i = 0; i <= MAX_RETRIES; i++) { try { return await request(url) } catch (err) { const timeout = Math.pow(2, i) console.log('Waiting', timeout, 'ms') await wait(timeout) console.log('Retrying', err.message, i) } } }
The above code looks very comfortable, right
Intermediate value
Not as scary as the previous example , if you have a situation where 3 async functions depend on each other in turn, then you have to choose from several ugly solutions.
functionA returns a Promise, then functionB needs this value and functioinC needs the values after functionA and functionB complete.
Option 1: then Christmas tree
function executeAsyncTask () { return functionA() .then((valueA) => { return functionB(valueA) .then((valueB) => { return functionC(valueA, valueB) }) }) }
Using this solution, we can get valueA and valueB in the third then, and then we can get the values of valueA and valueB like the previous two thens. . You can't flatten the Christmas tree (ruin hell) here, if you do you'll lose the closure and valueA won't be available in functioinC.
Option 2: Move to the upper level scope
function executeAsyncTask () { let valueA return functionA() .then((v) => { valueA = v return functionB(valueA) }) .then((valueB) => { return functionC(valueA, valueB) }) }
In this Christmas tree, we use the higher scope retainer valueA, because the valueA scope is in all then scopes outside, so functionC can get the value of the first functionA to complete.
This is a very "correct" syntax for flattening the .then chain, however, with this approach we need to use two variables valueA and v to hold the same value.
Option 3: Use an extra array
function executeAsyncTask () { return functionA() .then(valueA => { return Promise.all([valueA, functionB(valueA)]) }) .then(([valueA, valueB]) => { return functionC(valueA, valueB) }) }
Use an array in the then of function functionA to return valueA and Promise together, which can effectively flatten the Christmas tree (callback hell).
Option 4: Write a helper function
const converge = (...promises) => (...args) => { let [head, ...tail] = promises if (tail.length) { return head(...args) .then((value) => converge(...tail)(...args.concat([value]))) } else { return head(...args) } } functionA(2) .then((valueA) => converge(functionB, functionC)(valueA))
This is feasible, write a helper function to shield the context variable declaration. But such code is very difficult to read, especially for people who are not familiar with these magics.
Use async/await Our problem magically disappears
async function executeAsyncTask () { const valueA = await functionA() const valueB = await functionB(valueA) return function3(valueA, valueB) }
Use async/await to handle multiple parallel requests
It’s similar to the above one, if you want to execute multiple requests at once Asynchronous tasks, and then using their values in different places can be easily done using async/await.
async function executeParallelAsyncTasks () { const [ valueA, valueB, valueC ] = await Promise.all([ functionA(), functionB(), functionC() ]) doSomethingWith(valueA) doSomethingElseWith(valueB) doAnotherThingWith(valueC) }
Array iteration method
You can use async functions in the map, filter, and reduce methods. Although they may not seem very intuitive, you can experiment with the following code in the console.
1.map
function asyncThing (value) { return new Promise((resolve, reject) => { setTimeout(() => resolve(value), 100) }) } async function main () { return [1,2,3,4].map(async (value) => { const v = await asyncThing(value) return v * 2 }) } main() .then(v => console.log(v)) .catch(err => console.error(err))
2.filter
function asyncThing (value) { return new Promise((resolve, reject) => { setTimeout(() => resolve(value), 100) }) } async function main () { return [1,2,3,4].filter(async (value) => { const v = await asyncThing(value) return v % 2 === 0 }) } main() .then(v => console.log(v)) .catch(err => console.error(err))
3.reduce
function asyncThing (value) { return new Promise((resolve, reject) => { setTimeout(() => resolve(value), 100) }) } async function main () { return [1,2,3,4].reduce(async (acc, value) => { return await acc + await asyncThing(value) }, Promise.resolve(0)) } main() .then(v => console.log(v)) .catch(err => console.error(err))
Solution:
[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ] [ 1, 2, 3, 4 ] 10
If it is map iteration In the data, you will see that the return value is [2, 4, 6, 8]. The only problem is that each value is wrapped in a Promise by the AsyncFunction function
So if you want to get their values, you need Pass an array to Promise.All() to unwrap a Promise.
main() .then(v => Promise.all(v)) .then(v => console.log(v)) .catch(err => console.error(err)) 一开始你会等待 Promise 解决,然后使用map遍历每个值 function main () { return Promise.all([1,2,3,4].map((value) => asyncThing(value))) } main() .then(values => values.map((value) => value * 2)) .then(v => console.log(v)) .catch(err => console.error(err))
This seems simpler?
The async/await version is still useful if you have a long-running synchronous logic and another long-running asynchronous task in your iterator
This approach When you can get the first value, you can start doing some calculations without having to wait for all Promises to complete before running your calculations. Although the result is wrapped in a Promise, it is faster if the results are executed sequentially.
Questions about filter
你可能发觉了,即使上面filter函数里面返回了 [ false, true, false, true ] , await asyncThing(value) 会返回一个 promise 那么你肯定会得到一个原始的值。你可以在return之前等待所有异步完成,在进行过滤。
Reducing很简单,有一点需要注意的就是需要将初始值包裹在 Promise.resolve 中
重写基于callback的node应用成
Async 函数默认返回一个 Promise ,所以你可以使用 Promises 来重写任何基于 callback 的函数,然后 await 等待他们执行完毕。在node中也可以使用 util.promisify 函数将基于回调的函数转换为基于 Promise 的函数
重写基于Promise的应用程序
要转换很简单, .then 将Promise执行流串了起来。现在你可以直接使用`async/await。
function asyncTask () { return functionA() .then((valueA) => functionB(valueA)) .then((valueB) => functionC(valueB)) .then((valueC) => functionD(valueC)) .catch((err) => logger.error(err)) }
转换后
async function asyncTask () { try { const valueA = await functionA() const valueB = await functionB(valueA) const valueC = await functionC(valueB) return await functionD(valueC) } catch (err) { logger.error(err) } } Rewriting Nod
使用 Async/Await 将很大程度上的使应用程序具有高可读性,降低应用程序的处理复杂度(如:错误捕获),如果你也使用 node v8+的版本不妨尝试一下,或许会有新的收获。
相关推荐:
The above is the detailed content of Async and Await function analysis in Node.js. For more information, please follow other related articles on the PHP Chinese website!