Heim  >  Artikel  >  Web-Frontend  >  Async- und Await-Funktionsanalyse in Node.js

Async- und Await-Funktionsanalyse in Node.js

小云云
小云云Original
2018-02-24 09:10:381531Durchsuche

Dieser Artikel führt Sie hauptsächlich in die relevanten Kenntnisse der Async- und Await-Funktionen in Node.js ein. Sie erfahren, wie Sie die Async-Funktion (async/await) in Node.js verwenden, um Callback oder Promise zu vereinfachen Referenz Als Referenzwert können Freunde, die es benötigen, darauf verweisen. Ich hoffe, es kann jedem helfen.

Asynchrone Sprachstrukturen gibt es bereits in anderen Sprachen, wie z. B. async/await von c#, den Coroutinen von Kotlin und den Goroutinen von go. Mit der Veröffentlichung von Node.js 8 ist auch die lang erwartete async-Funktion implementiert Standard.

Was ist die asynchrone Funktion in Node?

Wenn eine Funktion als Async-Funktion deklariert wird, gibt sie ein AsyncFunction-Objekt zurück. Sie ähneln Generatoren darin, dass die Ausführung angehalten werden kann. Der einzige Unterschied besteht darin, dass sie ein Promise anstelle eines { value: any, done: Boolean }-Objekts zurückgeben. Sie sind sich jedoch immer noch sehr ähnlich und Sie können das Co-Paket verwenden, um die gleiche Funktionalität zu erhalten.

In einer asynchronen Funktion können Sie warten, bis das Versprechen abgeschlossen ist, oder den Grund für seine Ablehnung erfassen.

Wenn Sie einen Teil Ihrer eigenen Logik in Promise implementieren möchten

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

Sie können async/await verwenden, um diesen Code wie synchron ausgeführten Code aussehen zu lassen

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 der alten v8-Version erhalten Sie eine Warnung, wenn eine Versprechenablehnung vorliegt, die nicht behandelt wird, und Sie müssen keine Funktion zum Abhören von Ablehnungsfehlern erstellen. Es wird jedoch empfohlen, in diesem Fall Ihre Bewerbung zu beenden. Denn wenn Sie Fehler nicht behandeln, befindet sich die Anwendung in einem unbekannten Zustand.

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

asynchrones Funktionsmuster

Beim Umgang mit asynchronen Vorgängen gibt es viele Beispiele dafür, dass sie wie synchroner Code aussehen. Wenn Sie Promise oder Rückrufe verwenden, um das Problem zu lösen, müssen Sie ein sehr komplexes Muster oder eine externe Bibliothek verwenden.

Es ist eine sehr komplizierte Situation, wenn Sie die asynchrone Erfassung von Daten in einer Schleife verwenden oder if-else-Bedingungen verwenden müssen.

Exponentieller Rollback-Mechanismus

Die Verwendung von Promise zur Implementierung der Rollback-Logik ist ziemlich umständlich.

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

Der Code ist sehr mühsam anzusehen, und Sie möchten ihn nicht sehen solcher Code. Wir können dieses Beispiel mit async/await wiederholen, um es einfacher zu machen

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

Der obige Code sieht sehr komfortabel aus, nicht wahr

Zwischenwert

Nicht wie das vorherige Beispiel Also Beängstigend, wenn Sie eine Situation haben, in der drei asynchrone Funktionen abwechselnd voneinander abhängen, müssen Sie aus mehreren hässlichen Lösungen wählen.

FunktionA gibt ein Versprechen zurück, dann benötigt FunktionB diesen Wert und FunktionC benötigt die Werte, nachdem FunktionA und FunktionB abgeschlossen sind.

Option 1: dann Weihnachtsbaum

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

Mit dieser Lösung können wir WertA und WertB im dritten Then erhalten, und dann können wir WertA und WertB wie die beiden vorherigen Then-Werte erhalten . Sie können den Weihnachtsbaum hier nicht platt machen (die Hölle ruinieren), sonst verlieren Sie den Abschluss und valueA ist in functioinC nicht verfügbar.

Option 2: Wechseln Sie zum Bereich der oberen Ebene

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

In diesem Weihnachtsbaum verwenden wir den höheren Bereichs-Retainer-WertA, da der Bereich von valueA in allen Bereichen außerhalb des Bereichs liegt FunktionC kann den Wert der ersten abgeschlossenen FunktionA abrufen.

Dies ist eine sehr „richtige“ Syntax zum Reduzieren der .then-Kette. Auf diese Weise müssen wir jedoch zwei Variablen valueA und v verwenden, um denselben Wert zu halten.

Option 3: Verwenden Sie ein zusätzliches Array

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

Verwenden Sie ein Array von functionA, um valueA und Promise zusammen zurückzugeben, was den Weihnachtsbaum effektiv platt machen kann (Callback-Hölle).

Option 4: Schreiben Sie eine Hilfsfunktion

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

Dies ist machbar. Schreiben Sie eine Hilfsfunktion, um die Kontextvariablendeklaration abzuschirmen. Aber ein solcher Code ist sehr schwer zu lesen, insbesondere für Leute, die mit dieser Magie nicht vertraut sind.

Mit async/await verschwanden unsere Probleme auf magische Weise.

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

Mit async/await mehrere parallele Anfragen bearbeiten

Es ist fast das Gleiche wie oben, wenn Sie so wollen Um dies gleichzeitig zu tun, können Sie mit async/await ganz einfach mehrere asynchrone Aufgaben ausführen und deren Werte dann an verschiedenen Stellen verwenden.

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

Array-Iterationsmethode

Sie können asynchrone Funktionen in Zuordnungs-, Filter- und Reduzierungsmethoden verwenden. Obwohl diese möglicherweise nicht sehr intuitiv erscheinen, können Sie mit dem folgenden Code in der Konsole experimentieren .

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

Lösung:

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

Wenn es sich um Karteniterationsdaten handelt, sehen Sie, dass der Rückgabewert [2, 4, 6, 8] ist. Das einzige Problem besteht darin, dass jeder Wert von der AsyncFunction-Funktion in ein Versprechen eingeschlossen wird

Wenn Sie also ihre Werte erhalten möchten, müssen Sie das Array an Promise.All() übergeben, um das Promise zu entpacken.

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

Das scheint einfacher zu sein?

Die Async/Await-Version ist immer noch nützlich, wenn Sie eine synchrone Logik mit langer Laufzeit und eine andere asynchrone Aufgabe mit langer Laufzeit in Ihrem Iterator haben

Auf diese Weise können Sie den ersten Wert erhalten Sie können mit einigen Berechnungen beginnen, ohne warten zu müssen, bis alle Versprechen abgeschlossen sind, bevor Sie Ihre Berechnungen ausführen. Obwohl das Ergebnis in ein Promise verpackt ist, ist es schneller, wenn die Ergebnisse nacheinander ausgeführt werden.

Fragen zum 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+的版本不妨尝试一下,或许会有新的收获。

相关推荐:

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

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

ES7的async/await用法实例详解

Das obige ist der detaillierte Inhalt vonAsync- und Await-Funktionsanalyse in Node.js. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn