Home > Web Front-end > JS Tutorial > body text

Async and Await function analysis in Node.js

小云云
Release: 2018-02-24 09:10:38
Original
1599 people have browsed it

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()
 })
}
Copy after login

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)
}
Copy after login

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)
})
Copy after login

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)
 })
Copy after login

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(&#39;Waiting&#39;, timeout, &#39;ms&#39;)
  await wait(timeout)
  console.log(&#39;Retrying&#39;, err.message, i)
 }
 }
}
Copy after login

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)
  })
 })
}
Copy after login

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)
 })
}
Copy after login

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)
 })
}
Copy after login

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))
Copy after login

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)
}
Copy after login

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)
}
Copy after login

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))
Copy after login

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))
Copy after login

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))
Copy after login

Solution:

[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
[ 1, 2, 3, 4 ]
10
Copy after login

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))
Copy after login

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))
}
Copy after login

转换后

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
Copy after login

使用 Async/Await 将很大程度上的使应用程序具有高可读性,降低应用程序的处理复杂度(如:错误捕获),如果你也使用 node v8+的版本不妨尝试一下,或许会有新的收获。

相关推荐:

ES6之async+await 同步/异步方案

NodeJs通过async和await处理异步的方法

ES7的async/await用法实例详解

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!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template