違いはありますか?
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];
この答えの目的のために、私はいくつかの方法例を使用します。
res(ms)
はミリ秒の整数を取り、そのミリ秒後に解決する約束を返す関数です。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
async function example() {
const start = Date.now()
let i = 0
function res(n) {
const id = ++i
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
console.log(`res #${id} called after ${n} milliseconds`, Date.now() - start)
}, n)
})
}
const data = await Promise.all([res(3000), res(2000), res(1000)])
console.log(`Promise.all finished`, Date.now() - start)
}
example()
これはPromise.all
が3秒後に内部の約束からのデータで解決されることを意味します。
しかし、 Promise.all
は "fail fast"の振る舞いをします :
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 function example() {
const start = Date.now()
let i = 0
function res(n) {
const id = ++i
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
console.log(`res #${id} called after ${n} milliseconds`, Date.now() - start)
}, n)
})
}
function rej(n) {
const id = ++i
return new Promise((resolve, reject) => {
setTimeout(() => {
reject()
console.log(`rej #${id} called after ${n} milliseconds`, Date.now() - start)
}, n)
})
}
try {
const data = await Promise.all([res(3000), res(2000), rej(1000)])
} catch (error) {
console.log(`Promise.all finished`, Date.now() - start)
}
}
example()
代わりに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
async function example() {
const start = Date.now()
let i = 0
function res(n) {
const id = ++i
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
console.log(`res #${id} called after ${n} milliseconds`, Date.now() - start)
}, n)
})
}
function rej(n) {
const id = ++i
return new Promise((resolve, reject) => {
setTimeout(() => {
reject()
console.log(`rej #${id} called after ${n} milliseconds`, Date.now() - start)
}, n)
})
}
try {
const delay1 = res(3000)
const delay2 = res(2000)
const delay3 = rej(1000)
const data1 = await delay1
const data2 = await delay2
const data3 = await delay3
} catch (error) {
console.log(`await finished`, Date.now() - start)
}
}
example()
私は@ zzzzBovの答えに同意しますが、Promise.allの「失敗するのが早い」という利点が唯一の違いではありません。コメントで何人かのユーザーはそれが否定的なシナリオでより速いときだけPromise.allを使用する理由を尋ねます(いくつかのタスクが失敗するとき)。そして私はなぜそうではないのですか? 2つの独立した非同期並列タスクがあり、最初のタスクが非常に長い時間で解決されたが、2番目のエラータスクが非常に短い時間ではなく「非常に長い時間」待機する理由実際のアプリケーションでは、否定的なシナリオを考慮する必要があります。しかし、OK - この最初の違いでは、Promise.allと複数の方法のどちらを使用するかを決定できます。
しかし、エラー処理を検討するときは、Promise.allを使用しなければなりません。複数の待ち状態で発生した非同期並列タスクのエラーを正しく処理することはできません。否定的なシナリオでは、どこでもtry/catchを使用しますが、常にUnhandledPromiseRejectionWarning
とPromiseRejectionHandledWarning
で終わります。それがPromise.allが設計された理由です。もちろん、process.on('unhandledRejection', err => {})
とprocess.on('rejectionHandled', err => {})
を使ってそのエラーを抑制できると誰かが言うこともできますが、それは良い習慣ではありません。私はインターネット上で、2つ以上の独立した非同期並列タスクのエラー処理をまったく考慮しないか、または誤った方法で考慮しない多くの例を見つけました - ちょうどtry/catchを使用してエラーを捕捉することを期待します。良い習慣を見つけることはほとんど不可能です。私がこの答えを書いているのはそのためです。
2つ以上の独立した非同期並列タスクには、多重要求を絶対に使用しないでください。エラーを真剣に処理できないからです。この使用例では、必ずPromise.all()を使用してください。Async/awaitは、Promiseの代わりにはなりません。これはpromiseの使い方のちょうどいい方法です...非同期コードはsyncスタイルで書かれていて、promiseに複数のthen
を避けることができます。
Promise.all()を使用してタスクのエラーを個別に処理することはできず、最初に拒否されたプロミスからのエラーのみを処理することができると言う人もいます。問題ではありません - 下記の「追加」の見出しを参照してください。
この非同期タスクを検討してください...
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と複数待機の間に違いはありません。両方の例とも5秒後にTask 1 succeed! Task 2 succeed!
で終わります。
// 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!
ここでは、複数の待機を並行して使用しているときに問題が発生していることに既に気付いているはずです。もちろんエラーを避けるために私たちはそれを扱うべきです!やってみよう...
// 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つのcatchを追加する必要があり、catchロジックを持つコードはコールバック内にあります(非同期スタイル)。 run
関数内でハンドルエラーを処理する必要はありません。非同期関数が自動的に実行するからです。task
関数の棄却はrun
関数の却下を引き起こします。コールバックを避けるために、同期スタイル(async/await + try/catch)try { await run(); } catch(err) { }
を使用できますが、この例ではメインスレッドでawait
を使用できないため不可能です。非同期関数でのみ使用されます(誰もメインスレッドをブロックしたくないので論理的です)。処理が同期スタイルで機能するかどうかをテストするには、別の非同期関数からrun
関数を呼び出すか、IIFE(即時呼び出し関数式):(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)
……?最初に、タスク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)
...上と同じです。
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つだけ」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
とは対照的に、await
のブロッキング性を実証するためのテストを実行しました。それは他の人たちと共に進みます。