如何用Promise.all模擬allSettled

用過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都等到了最後。唯一需要注意的是這裏即使有子任務失敗,最終總的結果也是成功的。需要的話,你可能需要另外的變量存儲子任務的結果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章