簡體   English   中英

await Promise.all() 和 multiple await 有什么區別?

[英]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等待一些延遲將在所有延遲完成后解決,但請記住它們是同時執行的:

示例#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 

 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的數據進行解析。

但是, 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 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 依次解析,這可能效率不高:

示例 #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 

 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,您將始終以UnhandledPromiseRejectionWarningPromiseRejectionHandledWarning結束。 這就是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可以並行運行“同步”阻塞。

下面的test1test2函數顯示了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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM