這篇將繼續介紹 Promise,內容包括 Promise 的幾個靜態方法的用法介紹,以及 Promise.all()
& Promise.race()
的實作。
這個語法在處理多個 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()
會回傳一個 Promise 物件。這裡的 iterable 物件僅處理陣列的 case。
.then()
執行後會回傳一個包含各 Promise 物件執行結果的陣列。根據以上的分析,我們可以初步寫出這樣的程式碼:
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.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.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.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 驗證一下:
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('Success').then(function(value) {
console.log(value); // "Success"
}, function(value) {
// not called
});
範例中 resolve() 接受了一個參數,而它會回傳一個 resolved 的 Promise 物件,所以後面接了 then() 函式,而 then() 的第一個參數 onFulfilled 函式將會接收到 resolve() 的值。
Promise.reject(new Error('fail')).then(function(error) {
// not called
}, function(error) {
console.log(error); // Stacktrace
});
和 Promise.resolve()
差異在它會回傳一個 rejected 的 Promise。