Maison  >  Article  >  interface Web  >  Analyse des fonctions Async et Await dans Node.js

Analyse des fonctions Async et Await dans Node.js

小云云
小云云original
2018-02-24 09:10:381532parcourir

Cet article vous présente principalement les connaissances pertinentes des fonctions Async et Await dans Node.js. Vous apprendrez à utiliser la fonction async (async/await) dans Node.js pour simplifier le rappel ou Promise. référence Pour valeur de référence, les amis qui en ont besoin peuvent s'y référer. J'espère que cela pourra aider tout le monde.

Les structures de langage asynchrones existent déjà dans d'autres langages, tels que async/await de c#, les coroutines de Kotlin et les goroutines de go. Avec la sortie de Node.js 8, la fonction async tant attendue en fait également partie. par défaut.

Qu'est-ce que la fonction asynchrone dans Node ?

Lorsqu'une fonction est déclarée comme fonction Async, elle renvoie un objet AsyncFunction. Elles sont similaires aux générateurs dans la mesure où l'exécution peut être suspendue. La seule différence est qu'ils renvoient une promesse au lieu d'un objet {value: any, done: Boolean}. Cependant, ils sont toujours très similaires et vous pouvez utiliser le package co pour obtenir les mêmes fonctionnalités.

Dans une fonction asynchrone, vous pouvez attendre que la promesse se termine ou capturer la raison de son rejet.

Si vous souhaitez implémenter une partie de votre propre logique dans 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()
 })
}

Vous pouvez utiliser async/await pour que ce code ressemble à du code exécuté de manière synchrone

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

Dans l'ancienne version v8, s'il y a un rejet de promesse qui n'est pas géré, vous recevrez un avertissement et vous n'aurez pas besoin de créer une fonction d'écoute des erreurs de rejet. Il est toutefois recommandé de quitter votre application dans ce cas. Car lorsque vous ne gérez pas les erreurs, l’application est dans un état inconnu.

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

Modèle de fonction asynchrone

Lorsqu'il s'agit d'opérations asynchrones, il existe de nombreux exemples de les faire ressembler à du code synchrone. Si vous utilisez Promise ou des rappels pour résoudre le problème, vous devez utiliser un modèle très complexe ou une bibliothèque externe.

C'est une situation très compliquée lorsque vous devez utiliser l'acquisition asynchrone de données en boucle ou utiliser des conditions if-else.

Mécanisme de restauration exponentielle

Utiliser Promise pour implémenter la logique de restauration est assez maladroit

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

Le code est très difficile à regarder et vous ne voulez pas le voir tel code. Nous pouvons refaire cet exemple en utilisant async/await pour le rendre plus simple

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

Le code ci-dessus a l'air très confortable, n'est-ce pas

Valeur intermédiaire

Pas comme l'exemple précédent Donc effrayant, si vous vous trouvez dans une situation où 3 fonctions asynchrones dépendent tour à tour les unes des autres, alors vous devez choisir parmi plusieurs solutions laides.

functionA renvoie une promesse, puis functionB a besoin de cette valeur et functioinC a besoin des valeurs une fois la fonctionA et la fonctionB terminées.

Option 1 : puis arbre de Noël

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

En utilisant cette solution, nous pouvons obtenir valeurA et valeurB dans le troisième puis, puis nous pouvons obtenir valeurA et valeurB comme les deux valeurs précédentes . Vous ne pouvez pas aplatir le sapin de Noël (ruiner l'enfer) ici, si vous le faites, vous perdrez la fermeture et valueA ne sera pas disponible dans functioinC.

Option 2 : Passer à la portée de niveau supérieur

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

Dans cet arbre de Noël, nous utilisons la valeur de retenue de portée supérieure, car la portée valueA est dans toutes les portées en dehors de la portée, donc functionC peut obtenir la valeur de la première fonctionA terminée.

Il s'agit d'une syntaxe très "correcte" pour aplatir la chaîne .then, cependant, de cette façon, nous devons utiliser deux variables valueA et v pour conserver la même valeur.

Option 3 : Utilisez un tableau supplémentaire

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

Utilisez un tableau dans then de functionA pour renvoyer valueA et Promise ensemble, ce qui peut efficacement aplatir le sapin de Noël (l'enfer de rappel) .

Option 4 : Écrire une fonction d'assistance

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

C'est faisable, écrivez une fonction d'assistance pour protéger la déclaration de variable contextuelle. Mais un tel code est très difficile à lire, surtout pour les personnes qui ne sont pas familiarisées avec ces magies.

Utiliser async/await nos problèmes ont disparu comme par magie

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

Utiliser async/await pour gérer plusieurs requêtes parallèles

C'est presque la même chose que celle ci-dessus, si vous voulez pour le faire en même temps L'exécution de plusieurs tâches asynchrones puis l'utilisation de leurs valeurs à différents endroits peuvent être facilement réalisées en utilisant async/await.

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

Méthode d'itération de tableau

Vous pouvez utiliser des fonctions asynchrones dans les méthodes de mappage, de filtrage et de réduction Bien qu'elles puissent ne pas sembler très intuitives, vous pouvez expérimenter le code suivant dans la 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

S'il s'agit de données d'itération de carte, vous verrez que la valeur de retour est [2, 4, 6, 8]. Le seul problème est que chaque valeur est enveloppée dans une Promise par la fonction AsyncFunction

Donc, si pour obtenir leurs valeurs, vous devez transmettre le tableau à Promise.All() pour déballer la promesse.

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

Cela semble plus facile ?

La version async/await est toujours utile si vous avez une logique synchrone de longue durée et une autre tâche asynchrone de longue durée dans votre itérateur

De cette façon, lorsque vous pouvez obtenir la première valeur, vous Vous pouvez commencer à effectuer certains calculs sans avoir à attendre que toutes les promesses soient terminées avant d'exécuter vos calculs. Bien que le résultat soit enveloppé dans une promesse, il est plus rapide s'il est exécuté séquentiellement.

Questions sur le filtre

你可能发觉了,即使上面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用法实例详解

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn