javascript - 我如何在不使用『快速失敗』行為的情況下同時等待多個 Promise?
我正在使用 async/await 來並行發送多個 api 調用:
async function foo(arr) {
const results = await Promise.all(arr.map(v => {
return doAsyncThing(v)
}))
return results
}
我知道,與 loops 不同,Promise.all 是並行執行的(也就是說,等待結果的部分是並行的)。
但我也知道:
如果其中一個元素被拒絕,Promise.all 就會被拒絕,並且 Promise.all 會快速失敗:如果你有四個在超時後解決的 Promise,而其中一個立即被拒絕,則 Promise.all 會立即被拒絕。
當我閱讀這個時,如果我使用 Promise.all 與 5 個 promises,而最先完成的一個返回 reject(),那麼其他 4 個實際上被取消,它們承諾的 resolve() 值就會丟失。
有沒有第三種方式?執行實際上是並行的,但一次失敗不會毀掉整個批次?
答案#1
雖然被接受的答案中的方法可以解決你的問題,但這是一種反模式。用錯誤來解決一個承諾並不是好的做法,而且有更乾淨的方法可以做到這一點。
你想做的事情,用偽代碼表示,就是:
fn task() {
result-1 = doAsync();
result-n = doAsync();
// handle results together
return handleResults(result-1, ..., result-n)
}
這可以簡單地透過 async/await 達成,而不需要使用 Promise.all。一個可行的範例:
console.clear();
function wait(ms, data) {
return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}
/**
* These will be run in series, because we call
* a function and immediately wait for each result,
* so this will finish in 1s.
*/
async function series() {
return {
result1: await wait(500, 'seriesTask1'),
result2: await wait(500, 'seriesTask2'),
}
}
/**
* While here we call the functions first,
* then wait for the result later, so
* this will finish in 500ms.
*/
async function parallel() {
const task1 = wait(500, 'parallelTask1');
const task2 = wait(500, 'parallelTask2');
return {
result1: await task1,
result2: await task2,
}
}
async function taskRunner(fn, label) {
const startTime = performance.now();
console.log(`Task ${label} starting...`);
let result = await fn();
console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } milliseconds with,`, result);
}
void taskRunner(series, 'series');
void taskRunner(parallel, 'parallel');注意:您需要一個啟用了 async/await 的瀏覽器才能運行此程式碼片段。
這樣你就可以簡單地使用 try/catch 來處理你的錯誤,並在 parallel 函數內返回部分結果。
答案#2
ES2020 包含 Promise.allSettled,它能做到你想要的。
Promise.allSettled([
Promise.resolve('a'),
Promise.reject('b')
]).then(console.log)輸出:
[
{
"status": "fulfilled",
"value": "a"
},
{
"status": "rejected",
"reason": "b"
}
]
但如果你想「自己動手做」,那麼你可以利用這一點:使用 Promise#catch 意味著該 promise 會被解決(除非你在 catch 中拋出異常或手動拒絕 promise 鏈),因此你不需要明確返回一個已解決的 promise。
所以,只要使用 catch 處理錯誤,你就可以達到你想要的效果。
請注意,如果您希望錯誤在結果中可見,您將必須決定一種呈現它們的慣例。
你可以使用 Array#map 對集合中的每個 promise 應用拒絕處理函數,並使用 Promise.all 等待它們全部完成。
範例
以下內容應該會被列印出來:
Elapsed Time Output
0 started...
1s foo completed
1s bar completed
2s bam errored
2s done [
"foo result",
"bar result",
{
"error": "bam"
}
]
async function foo() {
await new Promise((r)=>setTimeout(r,1000))
console.log('foo completed')
return 'foo result'
}
async function bar() {
await new Promise((r)=>setTimeout(r,1000))
console.log('bar completed')
return 'bar result'
}
async function bam() {
try {
await new Promise((_,reject)=>setTimeout(reject,2000))
} catch {
console.log('bam errored')
throw 'bam'
}
}
function handleRejection(p) {
return p.catch((error)=>({
error
}))
}
function waitForAll(...ps) {
console.log('started...')
return Promise.all(ps.map(handleRejection))
}
waitForAll(foo(), bar(), bam()).then(results=>console.log('done', results))參見。
