![](/img/trans.png)
[英]Is there any difference between await Promise.all() and Promise.all() with await inside?
[英]Any difference between await Promise.all() and multiple await?
兩者之間有什么區別嗎:
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];
注意:
這個答案只涵蓋了
await
in series 和Promise.all
之間的時間差異。 請務必閱讀@mikep 的綜合答案,其中還涵蓋了錯誤處理中更重要的差異。
出於此答案的目的,我將使用一些示例方法:
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
的數據進行解析。
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
,您將不得不等待每個 promise 依次解析,這可能效率不高:
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
是值得的,因為它只會在負面情況下更快(當某些任務失敗時)。 我問,為什么不呢? 如果我有兩個獨立的異步並行任務,第一個需要很長時間才能解決,但第二個在很短的時間內被拒絕,為什么讓用戶等待更長的調用完成以接收錯誤消息? 在實際應用中,我們必須考慮負面情況。 但是好的 - 在第一個區別中,您可以決定使用哪種替代方案: Promise.all
與多個await
。
但是在考慮錯誤處理時,您必須使用Promise.all
。 無法正確處理由多個await
觸發的異步並行任務的錯誤。 在消極的情況下,無論您在何處使用 try/catch,您將始終以UnhandledPromiseRejectionWarning
和PromiseRejectionHandledWarning
結束。 這就是Promise.all
被設計的原因。 當然有人會說我們可以使用process.on('unhandledRejection', err => {})
和process.on('rejectionHandled', err => {})
來抑制這些錯誤process.on('rejectionHandled', err => {})
但這不是一個好習慣。 我在互聯網上發現了許多示例,它們根本不考慮對兩個或多個獨立異步並行任務進行錯誤處理,或者以錯誤的方式考慮它——只是使用 try/catch 並希望它能捕獲錯誤。 在這方面幾乎不可能找到好的做法。
TL;DR:切勿對兩個或多個獨立的異步並行任務使用多個await
,因為您將無法正確處理錯誤。 對於此用例,始終使用Promise.all()
。
Async/ await
不是 Promises 的替代品,它只是使用 Promises 的一種很好的方式。 異步代碼以“同步風格”編寫,我們可以避免在 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
和多個await
之間沒有區別。 兩個示例都以Task 1 succeed! Task 2 succeed!
結束Task 1 succeed! Task 2 succeed!
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
函數中添加一個 catch 並將帶有 catch 邏輯的代碼添加到回調中。 我們不需要在run
函數內部處理錯誤,因為異步函數會自動執行此操作 - 承諾拒絕task
函數會導致拒絕run
函數。
為了避免回調,我們可以使用“同步樣式”(async/ await
+ try/catch)
try { await run(); } catch(err) { }
但在這個例子中這是不可能的,因為我們不能在主線程中使用await
- 它只能在異步函數中使用(因為沒有人想阻塞主線程)。 要測試處理是否以“同步樣式”工作,我們可以從另一個異步函數調用run
函數或使用 IIFE(立即調用函數表達式: MDN ):
(async function() {
try {
await run();
} catch(err) {
console.log('Caught error', err);
}
})();
這是運行兩個或多個異步並行任務和處理錯誤的唯一正確方法。 你應該避免下面的例子。
// 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)
……和上面一樣。 用戶@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)
...“僅”兩個錯誤(第三個錯誤)但沒有發現任何錯誤。
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!
...請注意,在此示例中,我拒絕了這兩個任務以更好地演示會發生什么( throw err
用於觸發最終錯誤)。
通常,使用Promise.all()
並行運行“異步”請求。 使用await
可以並行運行或“同步”阻塞。
下面的test1和test2函數顯示了await
如何運行異步或同步。
test3顯示Promise.all()
是異步的。
帶有定時結果的 jsfiddle - 打開瀏覽器控制台查看測試結果
同步行為。 不並行運行,需要約1800 毫秒:
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
};
異步行為。 並行運行,大約需要600 毫秒:
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
}
異步行為。 並行運行,大約需要600 毫秒:
const test3 = async () => {
await Promise.all([
Promise.delay(600),
Promise.delay(600),
Promise.delay(600)]); //runs all delays simultaneously
};
TLDR; 如果您使用Promise.all()
它也會“快速失敗” - 在任何包含的函數第一次失敗時停止運行。
你可以自己查一下。
在這個fiddle 中,我運行了一個測試來證明await
的阻塞性質,而不是Promise.all
它將啟動所有的承諾,而當一個人在等待時,它會繼續其他人。
以防萬一,除了已經很棒的答案:
const rejectAt = 3; // No worries. "3" is purely awesome, too. Just for the tiny example. document;body.innerHTML = '': o("// With 'Promise;all()'."), let a = Promise,all([ test(1), test(2), test(3), test(4). test(5): ]);then(v => { o(`+ Look. We got all; ${v}`). }):catch(e => { o(`x Oh; Got rejected with '${e}'`); }).finally(() => { o("\n// With 'await';"). async function test2() { try { r = []; r.push(await test(1)); r.push(await test(2)); r.push(await test(3)); r:push(await test(4)). r,push(await test(5)); o(`+ Look; We got all; ${r;join(';')} // Twice as happy, ^^`); } catch (e) { o(`x Ah; Got rejected with '${e}'`), } } test2(); }). function test(v) { if (v === rejectAt) { o(`- Test ${v} (reject)`); return new Promise((undefined, reject) => reject(v)); } o(`- Test ${v} (resolve)`); return new Promise((resolve, undefined) => resolve(v)); } // ---------------------------------------- // Output function o(value) { document.write(`${value}\n`); }
body { white-space: pre; font-family: 'monospace'; }
一個可能的結果:
// With 'Promise.all()':
- Test 1 (resolve)
- Test 2 (resolve)
- Test 3 (reject)
- Test 4 (resolve)
- Test 5 (resolve)
x Oh! Got rejected with '3'
// With 'await':
- Test 1 (resolve)
- Test 2 (resolve)
- Test 3 (reject)
x Ah! Got rejected with '3'
如果是await Promise.all([task1(), task2()]); “task1()”和“task2()”將並行運行並等待兩個promise完成(解決或拒絕)。 而在情況下
const result1 = await t1;
const result2 = await t2;
t2 只會在 t1 完成執行(已解決或拒絕)后運行。 t1 和 t2 不會並行運行。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.