Node.js での Async および Await 関数の分析

小云云
リリース: 2018-02-24 09:10:38
オリジナル
1600 人が閲覧しました

この記事では、Node.js の Async 関数と Await 関数の関連知識を主に紹介します。Node.js で async 関数 (async/await) を使用してコールバックや Promise を簡素化する方法を学びます。参考値、困っている友達が参考になれば幸いです。

非同期言語構造は、C# の async/await、Kotlin のコルーチン、go の goroutine など、他の言語にすでに存在しています。Node.js 8 のリリースでは、待望の async 関数もデフォルトで実装されました。

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 つの async 関数がある場合、前の例ほど怖くありません他の場合は、いくつかの醜い解決策から選択する必要があります。

functionA は Promise を返し、functionB はこの値を必要とし、functionA と functionB が完了した後の functionC は値を必要とします。

オプション 1: 次にクリスマスツリー

function executeAsyncTask () {
 return functionA()
 .then((valueA) => {
  return functionB(valueA)
  .then((valueB) => {   
   return functionC(valueA, valueB)
  })
 })
}
ログイン後にコピー

この解決策を使用すると、3 番目の then で valueA と valueB を取得でき、その後、前の 2 つの 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 チェーンをフラット化するための非常に「正しい」構文ですが、この方法では同じ値を保持するために 2 つの変数 valueA と v を使用する必要があります。

オプション 3: 追加の配列を使用する

function executeAsyncTask () {
 return functionA()
 .then(valueA => {
  return Promise.all([valueA, functionB(valueA)])
 })
 .then(([valueA, valueB]) => {
  return functionC(valueA, valueB)
 })
}
ログイン後にコピー

functionA の then で配列を使用して、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)
}
ログイン後にコピー

配列反復メソッド

map、filter、reduce メソッドで非同期関数を使用できます。あまり直感的ではないように見えますが、コンソールで次のコードを試すことができます。

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 のラップを解除する必要があります。

rreee

これは簡単だと思いますか?

イテレーター内に長時間実行される同期ロジックと別の長時間実行される非同期タスクがある場合、async/await バージョンは引き続き役立ちます

このようにして、最初の値を取得できたら、何もしなくても計算を開始できます。計算を実行する前に、すべての Promise が完了するまで待機します。結果は 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 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート