用過Promise 的同學都知道Promise.all的作用是把一系列的異步(Promise)對象一起執行,等待所有都成功才成功,但如果某個失敗了就會立刻失敗,不會等待其他未完成的任務。
不過我們有時候會想要不同的行爲,就是執行一系列的異步任務,我們希望不論裏面的任務成功失敗,等到所有任務都結束後再結束。於是有了新的API “allSettled”。
不過現在這個API還是draft狀態,只有最新的瀏覽器和Node js 版本才支持。如果我們要兼容舊的瀏覽器和Node js版本咋辦呢?
答案當然是利用現有的API自己寫一個類似功能的。我看到網上有人的解決方案是不用Promise.all自己計數:記下開始啓動幾個異步任務,每完成(成功或者失敗都算)一個就減一,直到計數爲0爲止。這個辦法讓我想起來Java 的Semaphore,感覺有點類似。
我的想法是還利用Promise.all但是需要繞過它快速失敗的機制,怎麼辦呢?其實也挺簡單:把裏面的子任務的promise對象再封裝一次,屏蔽掉其中的失敗事件即可。
下面我們來看具體的例子,我們先看普通的Promise.all 快速失敗的情況:
function getTask(n) {
return new Promise((success, fail) => {
setTimeout(() => {
if (Math.random() > 0.5) {//隨機模擬任務成功失敗
console.log('Task ' + n + ' success!');//子任務成功
success();
} else {
console.log('Task ' + n + ' failed!');//子任務失敗
fail();
}
}, Math.random() * 3000);//隨機模擬任務執行時間
});
}
var taskArray = [];
for (let i = 0; i < 10; i++) {//生成一批子任務
taskArray.push(getTask(i));
}
Promise.all(taskArray).then(() => console.log("===All done===")).catch(() => console.log("===Already Failed==="));
執行結果:
Task 6 failed!
VM165:20 ===Already Failed===
VM165:8 Task 2 failed!
VM165:5 Task 0 success!
VM165:8 Task 8 failed!
VM165:5 Task 3 success!
VM165:8 Task 4 failed!
VM165:5 Task 1 success!
VM165:8 Task 5 failed!
VM165:8 Task 9 failed!
VM165:8 Task 7 failed!
我們看到這裏第一個結束的任務#6就失敗了,Promise.all立刻觸發了失敗,沒有等後面的任務結束。
我們照上面說的方法來修改一下程序,把子任務封裝一下再試一次:
function getTask(n) {
return new Promise((success, fail) => {
setTimeout(() => {
if (Math.random() > 0.5) {
console.log('Task ' + n + ' success!');
success();
} else {
console.log('Task ' + n + ' failed!');
fail();
}
}, Math.random() * 3000);
});
}
var taskArray = [];
for (let i = 0; i < 10; i++) {
taskArray.push(new Promise((success, fail) => {//嵌套一層Promise封裝子任務。
getTask(i).then(() => {
success();
}).catch(() => {
success();//屏蔽掉子任務的失敗事件。
});
}));
}
Promise.all(taskArray).then(() => console.log("===All done===")).catch(() => console.log("===Already Failed==="));
運行結果:
Task 1 failed!
VM170:8 Task 9 failed!
VM170:8 Task 6 failed!
VM170:5 Task 4 success!
VM170:5 Task 0 success!
VM170:8 Task 2 failed!
VM170:8 Task 7 failed!
VM170:8 Task 5 failed!
VM170:8 Task 3 failed!
VM170:5 Task 8 success!
VM170:26 ===All done===
我們看到這次無論子任務成功還是失敗,Promise.all都等到了最後。唯一需要注意的是這裏即使有子任務失敗,最終總的結果也是成功的。需要的話,你可能需要另外的變量存儲子任務的結果。