回答:
注:
この回答は
await
、シリーズとのタイミングの違いをカバーしていPromise.all
ます。@mikepの包括的な回答を必ず読んでください。エラー処理のより重要な違いについても説明しています。
この回答の目的のために、いくつかのサンプルメソッドを使用します。
res(ms)
ミリ秒の整数を取り、そのミリ秒後に解決するpromiseを返す関数です。rej(ms)
ミリ秒の整数を受け取り、そのミリ秒後に拒否するプロミスを返す関数です。呼び出しres
はタイマーを開始します。使用するPromise.all
していくつかの遅延を待機すると、すべての遅延が終了した後に解決しますが、同時に実行されることに注意してください。
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
「フェイルファースト」の動作があります。
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
代わりに使用する場合は、各プロミスが順番に解決されるのを待つ必要がありますが、効率的ではない場合があります。
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
unhandledrejection
エラーが発生します。あなたはこれを使いたくないでしょう。これを回答に追加してください。
私は@zzzzBovの答えに同意しますが、Promise.allの「すぐに失敗する」利点は唯一の違いだけではありません。コメントの一部のユーザーは、否定的なシナリオ(一部のタスクが失敗した場合)でのみ高速である場合にPromise.allを使用する理由を尋ねます。そして私はなぜそうしないのですか?2つの独立した非同期並列タスクがあり、最初のタスクは非常に長い時間で解決されますが、2番目は非常に短時間で拒否された場合、ユーザーに「非常に短い時間」ではなく「非常に長い時間」のエラーメッセージを待機させる理由は何ですか?実際のアプリケーションでは、否定的なシナリオを考慮する必要があります。しかし、OK-この最初の違いでは、Promise.allを使用するか、複数の待機を使用するかを決定できます。
ただし、エラー処理を検討する場合は、Promise.allを使用する必要があります。複数の待機でトリガーされた非同期並列タスクのエラーを正しく処理することはできません。否定的なシナリオではUnhandledPromiseRejectionWarning
、PromiseRejectionHandledWarning
どこでも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
使用して、何が起こるかをよりよくデモンストレーションしていることに注意してください(最終的なエラーを発生させるために使用されます)
一般に、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」も発生します- 含まれている関数のいずれかが最初に失敗したときに実行を停止します。
自分で確認できます。
このフィドルで、私はのブロッキングの性質を実証するためのテストを実行await
しました。Promise.all
これは、すべての約束を開始し、一方が待機している間、他の約束が続くこととは対照的です。
t1 = task1(); t2 = task2()
とその後のawait
両方の使用には違いがありresult1 = await t1; result2 = await t2;
ます。彼の質問のコードは、すべての約束を一度に開始します。答えが示すように、違いは障害が方法でより速く報告されることです。await
result1 = await task1(); result2 = await task2();
Promise.all
待機中の場合Promise.all([task1()、task2()]); 「task1()」と「task2()」は並行して実行され、両方のpromiseが完了する(解決または拒否される)まで待機します。一方、
const result1 = await t1;
const result2 = await t2;
t2は、t1の実行が終了した(解決または拒否された)後にのみ実行されます。t1とt2はどちらも並列実行されません。