>웹 프론트엔드 >JS 튜토리얼 >Node.js의 Async 및 Await 기능 분석

Node.js의 Async 및 Await 기능 분석

小云云
小云云원래의
2018-02-24 09:10:381605검색

이 글에서는 주로 Node.js의 Async 및 Await 기능에 대한 관련 지식을 소개합니다. Node.js의 비동기 기능(async/await)을 사용하여 콜백이나 Promise를 단순화하는 방법을 배우게 됩니다. 참조 값. 도움이 필요한 친구가 참조할 수 있으므로 모든 사람에게 도움이 되기를 바랍니다.

C#의 async/await, Kotlin의 코루틴, go의 고루틴 등 다른 언어에는 비동기식 언어 구조가 이미 존재합니다. Node.js 8이 출시되면서 오랫동안 기다려온 비동기 기능도 기본적으로 구현되었습니다.

Node의 비동기 기능이 무엇인가요?

함수가 Async 함수로 선언되면 AsyncFunction 개체를 반환하며, 실행을 일시 중지할 수 있다는 점에서 Generator와 유사합니다. 유일한 차이점은 { value: any, done: Boolean } 객체 대신 Promise를 반환한다는 것입니다. 그래도 여전히 매우 유사하므로 co 패키지를 사용하여 동일한 기능을 얻을 수 있습니다.

비동기 함수에서는 Promise가 완료될 때까지 기다리거나 거부된 이유를 캡처할 수 있습니다.

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()
 })
}

async/await를 사용하여 이 코드를 동기적으로 실행되는 코드처럼 보이게 만들 수 있습니다

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)
}

이전 v8 버전에서는 Promise 거부가 있는 경우 거기에 있습니까? ? 처리되면 경고가 표시되며 거부 오류 리스너 함수를 만들 필요가 없습니다. 그러나 이 경우에는 애플리케이션을 종료하는 것이 좋습니다. 오류를 처리하지 않으면 애플리케이션이 알 수 없는 상태이기 때문입니다.

process.on('unhandledRejection', (err) => { 
 console.error(err)
 process.exit(1)
})

async 함수 패턴

비동기 작업을 다룰 때 동기 코드처럼 보이게 만드는 예가 많이 있습니다. 문제를 해결하기 위해 Promise나 콜백을 사용하는 경우 매우 복잡한 패턴이나 외부 라이브러리를 사용해야 합니다.

루프에서 비동기 데이터 수집을 사용해야 하거나 if-else 조건을 사용해야 하는 경우 매우 복잡한 상황입니다.

지수 롤백 메커니즘

Promise를 사용하여 롤백 논리를 구현하는 것은 꽤 서투릅니다.

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)
 })

코드는 보기 매우 까다로우며 이러한 코드는 보고 싶지 않습니다. async/await를 사용하여 이 예제를 다시 실행하여 더 간단하게 만들 수 있습니다

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)
 }
 }
}

위 코드는 매우 편안해 보이죠?

중간 값

각각에 의존하는 3개의 비동기 함수가 있는 경우 이전 예제만큼 무섭지 않습니다. 다른 상황에서는 여러 가지 추악한 솔루션 중에서 선택해야 합니다.

functionA가 Promise를 반환하면 functionB에는 이 값이 필요하고 functioinC에는 functionA와 functionB가 완료된 후의 값이 필요합니다.

옵션 1: 그러면 크리스마스 트리

function executeAsyncTask () {
 return functionA()
 .then((valueA) => {
  return functionB(valueA)
  .then((valueB) => {   
   return functionC(valueA, valueB)
  })
 })
}

이 솔루션을 사용하면 세 번째 then에서 valueA와 valueB를 얻을 수 있고, 그런 다음 이전 두 then과 마찬가지로 valueA와 valueB의 값을 얻을 수 있습니다. 여기서는 크리스마스 트리를 평면화할 수 없습니다(파멸 지옥). 그렇게 하면 클로저를 잃게 되고 valueA는 functionC에서 사용할 수 없게 됩니다.

옵션 2: 상위 수준 범위로 이동

function executeAsyncTask () {
 let valueA
 return functionA()
 .then((v) => {
  valueA = v
  return functionB(valueA)
 })
 .then((valueB) => {
  return functionC(valueA, valueB)
 })
}

이 크리스마스 트리에서는 더 높은 범위의 보유 변수 valueA를 사용합니다. 왜냐하면 valueA의 범위가 모든 범위 외부에 있으므로 functionC는 functionA가 완성한 첫 번째 A 값을 얻을 수 있기 때문입니다. .

이것은 .then 체인을 평탄화하기 위한 매우 "올바른" 구문이지만, 이 방법에서는 동일한 값을 유지하기 위해 두 개의 변수 valueA와 v를 사용해야 합니다.

옵션 3: 추가 배열 사용

function executeAsyncTask () {
 return functionA()
 .then(valueA => {
  return Promise.all([valueA, functionB(valueA)])
 })
 .then(([valueA, valueB]) => {
  return functionC(valueA, valueB)
 })
}

functionA의 배열을 사용하여 valueA와 Promise를 함께 반환하면 크리스마스 트리를 효과적으로 평면화할 수 있습니다(콜백 지옥).

옵션 4: 도우미 함수 작성

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))

이것은 가능합니다. 컨텍스트 변수 선언을 마스크하는 도우미 함수를 작성하세요. 그러나 이러한 코드는 읽기가 매우 어렵습니다. 특히 이러한 마법에 익숙하지 않은 사람들에게는 더욱 그렇습니다.

async/await를 사용하면 문제가 마술처럼 사라졌습니다

async function executeAsyncTask () {
 const valueA = await functionA()
 const valueB = await functionB(valueA)
 return function3(valueA, valueB)
}

async/await를 사용하여 여러 병렬 요청 처리

위의 것과 유사합니다. 한 번에 여러 비동기 작업을 실행한 다음 다른 위치에서 사용하려는 경우 값 ​async/await를 사용하여 쉽게 확인할 수 있습니다.

async function executeParallelAsyncTasks () {
 const [ valueA, valueB, valueC ] = await Promise.all([ functionA(), functionB(), functionC() ])
 doSomethingWith(valueA)
 doSomethingElseWith(valueB)
 doAnotherThingWith(valueC)
}

배열 반복 방법

맵, 필터 및 축소 방법에서 비동기 함수를 사용할 수 있습니다. 비록 직관적이지 않을 수 있지만 콘솔에서 다음 코드를 실험해 볼 수 있습니다.

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))

해결 방법:

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

맵 반복 데이터인 경우 반환 값이 [2, 4, 6, 8]임을 알 수 있습니다. 유일한 문제는 각 값이 AsyncFunction 함수에 의해 Promise에 래핑된다는 것입니다

그래서 값을 얻으려면 Promise.All()에 배열을 전달하여 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))

이게 더 쉬울 것 같나요?

반복자에 장기 실행 동기 논리와 또 다른 장기 실행 비동기 작업이 있는 경우 async/await 버전이 여전히 유용한 경우가 많습니다.

이렇게 하면 첫 번째 값을 얻을 수 있을 때 별도의 작업 없이 일부 계산을 시작할 수 있습니다. 계산을 실행하기 전에 모든 약속이 완료될 때까지 기다려야 합니다. 결과가 Promise로 래핑되더라도 결과가 순차적으로 실행되면 더 빠릅니다.

필터에 관한 질문

你可能发觉了,即使上面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+的版本不妨尝试一下,或许会有新的收获。

相关推荐:

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

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

ES7的async/await用法实例详解

위 내용은 Node.js의 Async 및 Await 기능 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.