await Promise.all()と複数のawaitの違いは何ですか?


181

違いはありますか:

const [result1, result2] = await Promise.all([task1(), task2()]);

そして

const t1 = task1();
const t2 = task2();

const result1 = await t1;
const result2 = await t2;

そして

const [t1, t2] = [task1(), task2()];
const [result1, result2] = [await t1, await t2];

回答:


209

この回答はawait、シリーズとのタイミングの違いをカバーしていPromise.allます。@mikepの包括的な回答を必ず読んでくださいエラー処理のより重要な違いについても説明しています


この回答の目的のために、いくつかのサンプルメソッドを使用します。

  • res(ms) ミリ秒の整数を取り、そのミリ秒後に解決するpromiseを返す関数です。
  • rej(ms) ミリ秒の整数を受け取り、そのミリ秒後に拒否するプロミスを返す関数です。

呼び出しresはタイマーを開始します。使用するPromise.allしていくつかの遅延を待機すると、すべての遅延が終了した後に解決しますが、同時に実行されることに注意してください。

例1
const data = await Promise.all([res(3000), res(2000), res(1000)])
//                              ^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^
//                               delay 1    delay 2    delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O           delay 2
// =========O                     delay 3
//
// =============================O Promise.all

この意味は Promise.all 3秒後に内部プロミスからのデータで解決されます。

しかし、Promise.all「フェイルファースト」の動作があります

例2
const data = await Promise.all([res(3000), res(2000), rej(1000)])
//                              ^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^
//                               delay 1    delay 2    delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O           delay 2
// =========X                     delay 3
//
// =========X                     Promise.all

async-await代わりに使用する場合は、各プロミスが順番に解決されるのを待つ必要がありますが、効率的ではない場合があります。

例3
const delay1 = res(3000)
const delay2 = res(2000)
const delay3 = rej(1000)

const data1 = await delay1
const data2 = await delay2
const data3 = await delay3

// ms ------1---------2---------3
// =============================O delay 1
// ===================O           delay 2
// =========X                     delay 3
//
// =============================X await


4
基本的に、Promise.allの「フェイルファスト」機能だけが違いますか。
マシュー

4
@mclzc例3では、delay1が解決するまで、以降のコード実行が停止されます。それは「代わりにasync-awaitを使用する場合、各プロミスが順番に解決されるのを待つ必要がある」というテキストにもあります
haggis

1
@Qback、動作を示すライブコードスニペットがあります。それを実行して、コードを再度読み取ることを検討してください。あなたは約束の流れがどのように振る舞うかを誤解する最初の人ではありません。デモで間違えたのは、同時に約束を始めていないということです。
zzzzBov

1
@zzzzBovその通りです。あなたはそれを同時に始めています。申し訳ありませんが、別の理由でこの質問に来たのですが、見落としました。
Qback

2
効率的でない可能性があります」-さらに重要なことに、unhandledrejectionエラーが発生します。あなたはこれを使いたくないでしょう。これを回答に追加してください。
ベルギ

87

最初の違い-すぐに失敗する

私は@zzzzBovの答えに同意しますが、Promise.allの「すぐに失敗する」利点は唯一の違いだけではありません。コメントの一部のユーザーは、否定的なシナリオ(一部のタスクが失敗した場合)でのみ高速である場合にPromise.allを使用する理由を尋ねます。そして私はなぜそうしないのですか?2つの独立した非同期並列タスクがあり、最初のタスクは非常に長い時間で解決されますが、2番目は非常に短時間で拒否された場合、ユーザーに「非常に短い時間」ではなく「非常に長い時間」のエラーメッセージを待機させる理由は何ですか?実際のアプリケーションでは、否定的なシナリオを考慮する必要があります。しかし、OK-この最初の違いでは、Promise.allを使用するか、複数の待機を使用するかを決定できます。

2番目の違い-エラー処理

ただし、エラー処理を検討する場合は、Promise.allを使用する必要があります。複数の待機でトリガーされた非同期並列タスクのエラーを正しく処理することはできません。否定的なシナリオではUnhandledPromiseRejectionWarningPromiseRejectionHandledWarningどこでもtry / catchを使用しますが、常に終了します。これが、Promise.allが設計された理由です。もちろん、誰かがエラーを抑制してprocess.on('unhandledRejection', err => {})process.on('rejectionHandled', err => {})、それは良い習慣ではありません。インターネット上で、2つ以上の独立した非同期並列タスクのエラー処理をまったく考慮していないか、誤った方法で考慮している多くの例を見つけました。良い習慣を見つけることはほとんど不可能です。それが私がこの答えを書いている理由です。

概要

エラーを真剣に処理することができないため、2つ以上の独立した非同期並列タスクに対して複数の待機を使用しないでください。この使用例では、常にPromise.all()を使用してください。 Async / awaitはPromiseの代わりにはなりません。これは、promiseの使い方としてはかなり良い方法です...非同期コードは同期スタイルで記述されており、複数のコードを回避できます。then promiseをます。

一部の人々は、Promise.all()を使用すると、タスクエラーを個別に処理することはできず、最初に拒否されたpromiseからのエラーのみを処理できると言います(そうです。問題ではありません-以下の「追加」の見出しを参照してください。

この非同期タスクを検討してください...

const task = function(taskNum, seconds, negativeScenario) {
  return new Promise((resolve, reject) => {
    setTimeout(_ => {
      if (negativeScenario)
        reject(new Error('Task ' + taskNum + ' failed!'));
      else
        resolve('Task ' + taskNum + ' succeed!');
    }, seconds * 1000)
  });
};

肯定的なシナリオでタスクを実行する場合、Promise.allと複数の待機の間に違いはありません。どちらの例もTask 1 succeed! Task 2 succeed!5秒後に終了します。

// Promise.all alternative
const run = async function() {
  // tasks run immediate in parallel and wait for both results
  let [r1, r2] = await Promise.all([
    task(1, 5, false),
    task(2, 5, false)
  ]);
  console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: Task 1 succeed! Task 2 succeed!
// multiple await alternative
const run = async function() {
  // tasks run immediate in parallel
  let t1 = task(1, 5, false);
  let t2 = task(2, 5, false);
  // wait for both results
  let r1 = await t1;
  let r2 = await t2;
  console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: Task 1 succeed! Task 2 succeed!

最初のタスクが肯定的なシナリオで10秒かかり、秒のタスクが否定的なシナリオで5秒かかる場合、発行されるエラーに違いがあります。

// Promise.all alternative
const run = async function() {
  let [r1, r2] = await Promise.all([
      task(1, 10, false),
      task(2, 5, true)
  ]);
  console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// multiple await alternative
const run = async function() {
  let t1 = task(1, 10, false);
  let t2 = task(2, 5, true);
  let r1 = await t1;
  let r2 = await t2;
  console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
// at 10th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!

複数のawaitを並行して使用すると、何かがおかしいことに気づくはずです。もちろん、エラーを回避するために処理する必要があります!やってみよう...


// Promise.all alternative
const run = async function() {
  let [r1, r2] = await Promise.all([
    task(1, 10, false),
    task(2, 5, true)
  ]);
  console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: Caught error Error: Task 2 failed!

エラーを正常に処理するには、run関数にキャッチを1つだけ追加する必要があります。キャッチロジックを含むコードはコールバック(非同期スタイル)にあります。run関数内でエラーを処理する必要はありません。これは、非同期関数が自動的に行うためです。関数のtask拒否は、run関数の拒否を引き起こします。コールバックを回避するために、同期スタイル(async / await + try / catch)を使用できますtry { await run(); } catch(err) { }が、この例ではawait、メインスレッドでは使用できないため不可能です-非同期関数でのみ使用できます(誰もしたくないので論理的です)ブロックメインスレッド)。処理が別の非同期関数からの同期スタイルには、次のように呼び出すことができます。run関数で機能するかどうかをテストするには、IIFE(Immediately Invoked Function Expression)を使用します(async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();

これは、2つ以上の非同期並列タスクを実行してエラーを処理する方法の1つにすぎません。以下の例は避けてください。


// multiple await alternative
const run = async function() {
  let t1 = task(1, 10, false);
  let t2 = task(2, 5, true);
  let r1 = await t1;
  let r2 = await t2;
  console.log(r1 + ' ' + r2);
};

上記のコードをいくつかの方法で処理することができます...

try { run(); } catch(err) { console.log('Caught error', err); };
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled 

...同期コードを処理するrunが非同期である ため、何も捕捉されなかった

run().catch(err => { console.log('Caught error', err); });
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: Caught error Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

... Wtf?最初に、タスク2のエラーが処理されず、後でキャッチされたことがわかります。誤解を招きやすく、まだコンソールのエラーでいっぱいです。この方法では使用できません。

(async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: Caught error Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

...上記と同じ。削除された回答のユーザー@Qwertyは、キャッチされたように見えるこの奇妙な動作について尋ねましたが、未処理のエラーもあります。run()はawaitキーワードを指定してオンラインで拒否され、run()を呼び出すときにtry / catchを使用してキャッチできるため、エラーをキャッチします。また、非同期タスク関数を(awaitキーワードなしで)同期的に呼び出しており、このタスクがrun()関数の外部で実行され、外部でも失敗するため、未処理のエラーが発生します。コードの一部がsetTimeout ...で実行される同期関数を呼び出すときに、try / catchでエラーを処理できない場合も同様ですfunction test() { setTimeout(function() { console.log(causesError); }, 0); }; try { test(); } catch(e) { /* this will never catch error */ }

const run = async function() {
  try {
    let t1 = task(1, 10, false);
    let t2 = task(2, 5, true);
    let r1 = await t1;
    let r2 = await t2;
  }
  catch (err) {
    return new Error(err);
  }
  console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

...「2つだけ」のエラー(3番目のエラーは欠落)ですが、何もキャッチされませんでした。


追加(タスクエラーを個別に処理し、最初の失敗エラーも処理する)

const run = async function() {
  let [r1, r2] = await Promise.all([
    task(1, 10, true).catch(err => { console.log('Task 1 failed!'); throw err; }),
    task(2, 5, true).catch(err => { console.log('Task 2 failed!'); throw err; })
  ]);
  console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Run failed (does not matter which task)!'); });
// at 5th sec: Task 2 failed!
// at 5th sec: Run failed (does not matter which task)!
// at 10th sec: Task 1 failed!

...この例では、両方のタスクでnegativeScenario = trueをthrow err使用して、何が起こるかをよりよくデモンストレーションしていることに注意してください(最終的なエラーを発生させるために使用されます)


14
現在受け入れられている回答は、エラー処理の非常に重要なトピックを見逃しているため、この回答は受け入れられた回答よりも優れています
chrishiestand

8

一般に、Promise.all()runsリクエストは「非同期」で並行してリクエストを送信します。使用してawait並列に実行することができOR遮断する「同期」です。

以下のtest1およびtest2関数は、await非同期または同期を実行する方法を示しています。

test3Promise.all()は非同期であることを示しています

jsfiddleと時間指定された結果 -ブラウザーコンソールを開いてテスト結果を表示

同期動作。並行して実行されず、約1800msかかります:

const test1 = async () => {
  const delay1 = await Promise.delay(600); //runs 1st
  const delay2 = await Promise.delay(600); //waits 600 for delay1 to run
  const delay3 = await Promise.delay(600); //waits 600 more for delay2 to run
};

非同期動作。paralelで実行し、〜とる600msのを

const test2 = async () => {
  const delay1 = Promise.delay(600);
  const delay2 = Promise.delay(600);
  const delay3 = Promise.delay(600);
  const data1 = await delay1;
  const data2 = await delay2;
  const data3 = await delay3; //runs all delays simultaneously
}

非同期動作。並行して実行され、約600msかかります:

const test3 = async () => {
  await Promise.all([
  Promise.delay(600), 
  Promise.delay(600), 
  Promise.delay(600)]); //runs all delays simultaneously
};

TLDR; 使用しPromise.all()ている場合は「fast-fail」も発生します- 含まれている関数のいずれかが最初に失敗したときに実行を停止します。


1
スニペット1と2の内部で何が起こるかについての詳細な説明はどこで入手できますか?動作が同じであることを期待していたので、これらの実行方法が異なることに驚きました。
Gregordy

2
@Gregordyはい、それは驚くべきことです。私はこの回答を投稿して、いくつかの頭痛の種を非同期にする新しいコーダーを救いました。JSが待機を評価するときがすべてです。これが、変数を割り当てる方法が重要な理由です。深さでは非同期で読み込む:blog.bitsrc.io/...
GavinBelson

7

自分で確認できます。

このフィドルで、私はのブロッキングの性質を実証するためのテストを実行awaitしました。Promise.allこれは、すべての約束を開始し、一方が待機している間、他の約束が続くこととは対照的です。


6
実際、あなたのフィドルは彼の質問に対応していません。のように元の呼び出しで使用しているテストとは対照的に、彼の質問のように、呼び出しt1 = task1(); t2 = task2()その後のawait両方の使用には違いがありresult1 = await t1; result2 = await t2;ます。彼の質問のコードは、すべての約束を一度に開始します。答えが示すように、違いは障害が方法でより速く報告されることです。awaitresult1 = await task1(); result2 = await task2();Promise.all
BryanGrezeszak 2018

あなたの答えは@BryanGrezeszakがコメントしたようなトピックから外れています。誤解を招くユーザーを回避するために、むしろそれを削除する必要があります。
mikep

0

待機中の場合Promise.all([task1()、task2()]); 「task1()」と「task2()」は並行して実行され、両方のpromiseが完了する(解決または拒否される)まで待機します。一方、

const result1 = await t1;
const result2 = await t2;

t2は、t1の実行が終了した(解決または拒否された)後にのみ実行されます。t1とt2はどちらも並列実行されません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.