iT邦幫忙

2022 iThome 鐵人賽

DAY 18
1
Modern Web

強化 JavaScript 之 - 程式語感是可以磨練成就的系列 第 18

Day18-JavaScript Promise 系列-Promise 的幾個靜態方法介紹

  • 分享至 

  • xImage
  •  

前言

這篇將繼續介紹 Promise,內容包括 Promise 的幾個靜態方法的用法介紹,以及 Promise.all() & Promise.race() 的實作。


Promise.all()

這個語法在處理多個 Promise 時相當有幫助,可以讓多個 Promise 同時執行,執行完成後回傳一個 Promise 物件,並透過 .then() 函式,例如 Promise.all().then((value) => console.log(value)),在 console.log 中能看到執行完回傳結果值組成的陣列,但若有任一個 Promise 失敗就會立刻 reject。

語法

Promise.all(iterable);,iterable 參數是一個 iterable 的物件,例如陣列、Map、Set,iterable 參數裡面的元素可以是多個 Promise 物件,如果不是 Promise 物件的元素會被轉化成 Promise 物件。

範例

可以試著執行以下程式,等幾秒後就會回傳結果。

function getPromise(i) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(i), Math.random() * 5000);
  })
}

const arr = [];

for (let i = 0; i < 5; i++) {
  arr.push(getPromise(i));
}

Promise.all(arr).then(values => {
  console.log(values); // [0, 1, 2, 3, 4]
}).catch((error) => {
  console.log(error);
});

接著讀者可以嘗試將其中一個 Promise 物件 p4 設定為一定會 reject 的話,會立即看到執行結果。

實作 Promise.all()

接著我們來實作看看 Promise.all(),透過實作的過程更了解 Promise.all()

第一階段

從前面的介紹中,我們知道:

  1. Promise.all() 會回傳一個 Promise 物件。
  2. 將一個包含多個 Promise 物件的 iterable 物件當作參數傳入。

這裡的 iterable 物件僅處理陣列的 case。

  1. 其回傳 Promise 物件透過 .then() 執行後會回傳一個包含各 Promise 物件執行結果的陣列。
  2. 陣列內任一個 Promise 發生錯誤,馬上 reject。

根據以上的分析,我們可以初步寫出這樣的程式碼:

function promiseAll(arr) {
  return new Promise((resolve, reject) => {
    arr.forEach((promiseObj) => {
      promiseObj().then((value) => {
          // ...
        })
        .catch((err) => reject(err));
        .finally(() => {
          // ...
        })
    })
  })
}

因為會包含各 Promise 物件執行結果的陣列,所以透過 forEach 去作遍歷,用 for ... of 做也是可以的喔~

第二階段

接著將每個 promiseObj 執行完後的值加入到 result 陣列,當 counter 加到和傳入的陣列長度相同時,就代表所有值都已經遍歷過,可以 resolve 了。

function promiseAll(arr) {
  return new Promise((resolve, reject) => {
    if (!arr.length) return resolve(arr);
    const result = [];
    let counter = 0;

    arr.forEach((promiseObj, index) => {
      promiseObj().then((value) => {
        result.push(value);
      })
      .catch(reject)
      .finally(() => {
        counter++;
        if (counter === arr.length) resolve(result);
        // 或是寫成一行: if (++counter === arr.length) resolve(result);
      })
    })
  })
}

第三階段

最後注意一下是否有處理到 edge case 即可。

const arr = []; // 空陣列的情況

// arr.push(Promise.reject('error')); // reject 的情況

function promiseAll(arr) { // 和第二階段的程式碼相同,故略... }

promiseAll(arr).then((value) => console.log(value))
  .catch((err) => console.log(err));
  
// 沒有傳入參數的情況
promiseAll().then((value) => console.log(value))
  .catch((err) => console.log(err));

讀者親自試試實作 Promise.all()

LeetCode 有出過實作 Promise.all() 的題目-2721. Execute Asynchronous Functions in Parallel
,讀者看完上面內容也可以親自試試看喔~


Promise.allSettled()

這裡補充個和 Promise.all() 類似的語法,Promise.allSettled() 在 ES2020 版本推出,它和 Promise.all() 一樣會同時執行多個 Promise,但不同的地方是 Promise.all() 有任一 Promise reject,就會直接進入 catch,也不會收到其他 Promise resolved 的結果。

Promise.allSettled() 執行多個 Promise 後,不論結果是 resolved 或是 rejected,都會回傳一個 Promise 物件。

並透過 .then() 函式執行 pending 的 Promise 物件後,在 console.log 中能看到回傳一個陣列,裡面包含各 Promise 執行結果的物件,就如讀者在範例中看到的結果。

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));

Promise.allSettled([promise1, promise2]).
  then((results) => console.log(results));

成功的結果出現 { status: "fulfilled", value: ... } 之類的物件,而失敗的結果則出現 { status: "rejected", reason: ... } 之類的物件。


Promise.race()

Promise.all() 不同的地方就是 Promise.race() 只回傳傳入的 Promie 陣列中第一個被 resolve/reject 的 Promise 物件。

以下範例將前面的 Promise.all() 換成 Promise.race():

範例

function getPromise(i) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(i), Math.random() * 5000);
  })
}

const arr = [];

for (let i = 0; i < 5; i++) {
  arr.push(getPromise(i));
}

Promise.race(arr).then(values => {
  console.log(values); // 不定值,最快完成的 promise resolve 的值
}).catch((error) => {
  console.log(error);
});

實作 Promise.race()

和前面一樣,來實作看看 Promise.race(),不過比起 Promise.all() 簡單多了,主要觀念就是回傳第一個執行完的 Promise 物件。

這裡的 iterable 物件僅處理陣列的 case。

function promiseRace(arr) {
  return new Promise((resolve, reject) => {
    for (const promiseObj of arr) {
      promiseObj.then((value) => resolve(value)).catch((err) => reject(err));
    }
  });
}

也用 edge case 驗證一下:

  1. 空陣列的情況,什麼值都不會印出來。
  2. reject 的情況,印出 'error' 字串。
const arr = []; // 空陣列的情況

// arr.push(Promise.reject('error')); // reject 的情況

function promiseRace(arr) { // 略... }

promiseRace(arr).then((value) => console.log(value))
  .catch((err) => console.log(err));

Promise.resolve()

以下直接從範例說明:

Promise.resolve('Success').then(function(value) {
  console.log(value); // "Success"
}, function(value) {
  // not called
});

範例中 resolve() 接受了一個參數,而它會回傳一個 resolved 的 Promise 物件,所以後面接了 then() 函式,而 then() 的第一個參數 onFulfilled 函式將會接收到 resolve() 的值。


Promise.reject()

Promise.reject(new Error('fail')).then(function(error) {
  // not called
}, function(error) {
  console.log(error); // Stacktrace
});

Promise.resolve() 差異在它會回傳一個 rejected 的 Promise。


參考資料 & 推薦閱讀

Implement Promise.all

Promise API


上一篇
Day17-JavaScript Promise 系列-認識 Promise
下一篇
Day19-JavaScript Promise 系列-更多關於 Promise 的練習
系列文
強化 JavaScript 之 - 程式語感是可以磨練成就的30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言