JSの非同期トラバーサル、本当に書けますか?

オファーが届きました。友達を掘って受け取りましょう!私は2022年春の採用チェックインイベントに参加しています。クリックしてイベントの詳細を表示します。

配列の要素をトラバースして、実行のために非同期関数に渡す必要がある場合があります。非同期書き込み方法は、間違って記述しやすいので、エラーが発生しやすいポイントを見てみましょう。

次の形式の非同期メソッドsleepPromiseがあるとします。

function sleepPromise(msg, t{
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`promise-${msg} ${t}s`);
    }, t * 1000);
  })
}
复制代码

デモンストレーションの便宜上、promise形式のsleepメソッドはsetTimeoutを使用して記述されています。着信tは遅延実行時間、msgは情報コンテンツです。

実際の開発では、非同期の方法は、ユーザーのフレンドIDを渡してデータベースを検索し、単純なフレンド情報を取得することです。

以下のコードのコメント位置の下に非同期の便利な実装を作成する必要があるとします。

async function loopAsync({
  console.log('[start]');
  const curTime = Date.now();

  const tasks = [
    ['a'3],
    ['b'1],
    ['c'2],
  ];
  
  // 下面写遍历 tasks 传入到异步方法的实现

  console.log(`[end] duration ${((Date.now() - curTime) / 1000).toFixed(2)}s`);
}
复制代码

間違ったスペル:forEach

通常、フロントエンドは、配列がトラバースされることを確認するとすぐにforEachを使用します。十分に洗練されていない場合は、次のような実装を作成できます。

// 错误的 forEach 写法
tasks.forEach(async task => {
  const [ msg, t ] = task;
  const m = await sleepPromise(msg, t);
  console.log(m);
})
复制代码

出力は次のとおりです。

[start]
[end] duration 0.00s
promise-b 1s
promise-c 2s
promise-a 3s
复制代码

この書き方は正しくありません。実際、トラバーサルは同期として書かれています。

どうしたの?forEach自体は非同期書き込みをサポートしていないため、forEachメソッドの前にawaitキーワードを追加しても、非同期ロジックがないため無効です。

forEachはES5APIであり、ES6Promiseよりもはるかに早いものです。下位互換性のために、forEachは将来的に非同期処理もサポートしなくなります。

したがって、forEachを実行しても、loopAsyncの後にコードがブロックされることはないため、ブロックが失敗し、最初に出力されます [end]

シリアル書き込み:forループ

// 串行写法
for (const task of tasks) {
  const [ msg, t ] = task;
  const m = await sleepPromise(msg, t);
  console.log(m);
}
复制代码

通常のforループ書き込みメソッドを使用すると、awaitの外部関数はloopAysncメソッドのままであり、ブロッキングコードを正しく保存できます。

ただし、ここでの問題は、これらの非同期メソッドの実行が シリアルであるということ です。合計6秒が実行されたことがわかります。

[start]
promise-a 3s
promise-b 1s
promise-c 2s
[end] duration 6.01s
复制代码

リクエストに順次依存関係がある場合、これは問題ありません。

しかし、私たちのシナリオがユーザーID配列に基づいてデータベースから対応するユーザー名を検索することである場合、時間の複雑さ O(n) は不合理です。

この時点で、並列非同期に書き換える必要があり  ます。また、次のステップを実行する前に、すべての非同期が実行されていることを確認する必要があります。使用できます Promise.all()

並列実装:Promise.all

// 并行写法
const taskPromises = tasks.map(task => {
  const [ msg, t ] = task;
  return sleepPromise(msg, t).then(m => {
    console.log(m);
  });
});
await Promise.all(taskPromises);
复制代码

まず、tasks配列に基づいて対応するpromiseオブジェクト配列を生成し、それをPromise.allメソッドに渡して実行する必要があります。

このように、これらの非同期メソッドは同時に実行されます。すべての非同期実行が完了すると、コードが実行されます。

出力は次のとおりです。

[start]
promise-b 1s
promise-c 2s
promise-a 3s
[end] duration 3.00s
复制代码

3秒で終わり、強すぎます。

forEachに戻る

前述のように、forEachの最下層は非同期処理を実装していないため、ブロッキングエラーが発生するため、非同期をサポートする単純なforEachを実装することをお勧めします。

並列実装:

async function forEach(arr, fn{
  const fns = [];
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i];
    fns.push(fn(item, i, arr));
  }
  await Promise.all(fns);
}
复制代码

シリアル実装:

async function forEach(arr, fn{
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i];
    await fn(item, i, arr);
  }
}
复制代码

利用方法:

await forEach(tasks, async task => {
  const [ msg, t ] = task;
  const m = await sleepPromise(msg, t);
  console.log(m);
})
复制代码

要約する

簡単に要約します。

一般的に、Promise.allの非同期メソッドを使用して並列実行します。これは、データベースがいくつかのIDに対応するデータを検索するシナリオでよく使用されます。

forループのシリアル書き込み方法は、最終的なリファラーの検索など、依存関係のある複数の非同期状況に適しています。

forEachは、async / awaitを使用する必要がない限り、純粋なタイプミスです。

私はフロントエンドのスイカの兄弟であり、フロントエンドの知識の共有に焦点を当てています。私をフォローすることを歓迎します。

おすすめ

転載: juejin.im/post/7078695117967589406
おすすめ