Promise 學習筆記二 :: 第一次 Promise 就上手

上篇說明了Promise規範產生的原因以及介紹Promise如何以簡潔的方式來管理非同步操作及鏈式執行,比起傳統的回調函式更加方便的靈活使用。
本篇介紹Promise常用的函式以及使用範例,希望透過這些範例可以讓你們能夠更加瞭解熟悉使用Promise,開發出來的程式有更高的閱讀性及維護性。
下表爲本文將介紹的函式功能一覽表:
函式 | 引入版本 | 簡單描述 |
---|---|---|
Promise.prototype.then() | ES6 | 對應resolve及reject的回呼函式,並返回一個新的Promise實現鏈式執行。 |
Promise.prototype.catch() | ES6 | 相當於是then(undefined, onRejected)的縮寫。 |
Promise.prototype.finally() | ES2018 | 相當於是then(onFinally, onFinally)的縮寫。 |
Promise.all | ES6 | 接收可迭代物件,直到所有Promise已解決或其中一個拒絕才回傳Promise。 |
Promise.allSettled | ES2020 | 接收可迭代物件,直到所有Promise已解決或拒絕才回傳Promise。 |
Promise.race | ES6 | 接收可迭代物件,第一個Promise已解決或拒絕才回傳Promise。 |
Promise.any | ES2021 | 接收可迭代物件,第一個Promise已解決或所有都拒絕或傳入可迭代物件才回傳Promise。 |
Promise.reject | ES6 | 相當於 new Promise((resolve, reject) => { resolve(); }); |
Promise.resolve | ES6 | 相當於 new Promise((resolve, reject) => { reject(); }); |
接下來將用實際程式碼範例來介紹。
Promise.prototype.then()
then()函式最多可以傳入兩個參數如下:
- onFulfilled (Optional)
- onRejected (Optional)
用於對應resolve, reject的回呼函式,且返回值是一個新的Promise用於實現鏈式執行。
若onFulfilled的參數型態不是function則會被替換成以下的函式。
(value) => {
return value
}
上方的函式會將原先的傳入的值往下一個Promise傳遞(穿透)。
與上同理若onRejected的參數型態不是function則會被替換成以下的函式。
(reason) => {
throw reason
}
上方的函式會將原先傳入的值拋出到下一個Promise(穿透)。
Promise.prototype.catch()
catch()函式在ES6中被引入,此函式主要用於處理Promise被拒絕時執行的函式。
catch()相當於是then(undefined, onRejected)的縮寫。
new Promise((resolve, reject) => {
reject('nicklabs.cc');
})
.then((value) => {
console.log(`success: `, value);
})
.catch((reason) => {
console.log(`error: `, reason);
});
// 輸出 error: nicklabs.cc
因為在then中未傳入onRejected函式,所以會將錯誤訊息將穿透到下一個then中,若then有實作onRejected函式則後方加上catch也不會執行。
new Promise((resolve, reject) => {
reject('nicklabs.cc');
})
.then(
(value) => {
console.log(`success: `, value);
},
(reason) => {
console.log(`error 1: `, reason);
}
)
.catch((reason) => {
console.log(`error 2: `, reason);
});
// 輸出 error 1: nicklabs.cc
若使用鏈式方式處理則可以選擇在最後面加上catch即可,不需在一個then中都實作onRejected函式。
Promise.prototype.finally()
finally函式在ES2018中被引入,此函式在Promise(已解決或拒絕)後觸發。
finally()相當於是then(onFinally, onFinally)的縮寫。
new Promise((resolve, reject) => {
resolve('nicklabs.cc');
})
.then((value) => {
console.log(`success: `, value);
})
.finally(() => {
console.log(`finally`);
});
// 輸出 success: nicklabs.cc
// 輸出 finally
這對於需要在Promise結束後執行某些操作時特別有用。
如透過API讀取資料時會先開啟Loading畫面直到response完成時才會關閉Loading畫面。
new Promise((resolve, reject) => {
console.log(`開啟Loading`);
resolve('nicklabs.cc');
})
.then((value) => {
console.log(`success: `, value);
})
.finally(() => {
console.log(`關閉Loading`);
});
// 輸出 開啟Loading
// 輸出 success: nicklabs.cc
// 輸出 關閉Loading
在Loading的情境下若沒有finally函式,則需要在onFulfilled及onRejected中都需要加入關閉Loading的程式,但使用了finally則可以避免書寫重複的程式碼。
Promise.all
Promise.all函式在ES6中被引入,接收一個可迭代物件並返回Promise,等待所有傳入的Promise都已解決並以陣列回傳結果,但若有任何一個拒絕則會直接返回Promise並附帶拒絕原因。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1');
}, 1000);
});
const p2 = Promise.resolve('p2');
const p3 = new Promise((resolve, reject) => {
resolve('p3');
});
const p4 = 'p4';
Promise.all([p1, p2, p3, p4])
.then((values) => {
console.log(values);
});
// 輸出 ['p1', 'p2', 'p3', 'p4']
若有一個拒絕則會回傳Promise並附帶拒絕原因。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1');
}, 1000);
});
const p2 = Promise.reject('p2');
const p3 = new Promise((resolve, reject) => {
resolve('p3');
});
const p4 = 'p4';
Promise.all([p1, p2, p3, p4])
.then((values) => {
console.log(values);
});
// 輸出 Promise {<rejected>: 'p2'}
適合在需要同時呼叫多個Promise時使用。
Promise.allSettled
Promise.allSettled函式在ES2020中被引入,接收一個可迭代物件並返回Promise,與all函式類似但差別在於需要等Promise都解決後(無論是已解決還是拒絕),才返回結果並附上Promise狀態便於判斷。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1');
}, 1000);
});
const p2 = Promise.resolve('p2');
const p3 = new Promise((resolve, reject) => {
resolve('p3');
});
const p4 = 'p4';
Promise.allSettled([p1, p2, p3, p4])
.then((values) => {
console.log(values);
});
// 輸出
//[
//{status: 'fulfilled', value: 'p1'},
//{status: 'fulfilled', value: 'p2'},
//{status: 'fulfilled', value: 'p3'},
//{status: 'fulfilled', value: 'p4'}
//]
若其中Proimse返回拒絕則可以從status判斷出來。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1');
}, 1000);
});
const p2 = Promise.reject('p2');
const p3 = new Promise((resolve, reject) => {
resolve('p3');
});
const p4 = 'p4';
Promise.allSettled([p1, p2, p3, p4])
.then((values) => {
console.log(values);
});
// 輸出
//[
//{status: 'fulfilled', value: 'p1'},
//{status: 'rejected', value: 'p2'},
//{status: 'fulfilled', value: 'p3'},
//{status: 'fulfilled', value: 'p4'}
//]
Promise.race
Promise.race函式在ES6中被引入,接收一個可迭代物件並返回第一個已解決或拒絕的Promise結果。
以下例子在p1在一秒後觸發resolve即達成返回條件。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1');
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2');
}, 2000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p3');
}, 3000);
});
const p4 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p4');
}, 4000);
});
Promise.race([p1, p2, p3, p4])
.then((value) => {
console.log(value);
});
// 輸出 p1
Promise.any
Promise.any函式在ES2021中被引入,接收一個可迭代物件並返回第一個已解決Promise的結果,若傳入的Promise都拒絕或是傳入空的可迭代物件,則會拋出AggregateError訊息。
以下例子p1建立Promise立即執行resolve達成返回條件。
const p1 = new Promise((resolve, reject) => {
resolve('p1');
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2');
}, 1000);
});
const p3 = new Promise((resolve, reject) => {
reject('p3');
});
Promise.any([p1, p2, p3])
.then((value) => {
console.log(value);
});
// 輸出 'p1'
以下例子兩個Promise皆觸發拒絕函式則拋出AggregateError訊息
const p4 = new Promise((resolve, reject) => {
reject('p4');
});
const p5 = new Promise((resolve, reject) => {
reject('p5');
});
Promise.any([p4, p5])
.then((value) => {
console.log(value);
});
// 拋出 'AggregateError: All promises were rejected'
以下例子傳入空的可迭代物件則拋出AggregateError訊息
Promise.any([])
.then((value) => {
console.log(value);
});
// 泡出 'AggregateError: All promises were rejected'
Promise.reject
Promise.reject函式在ES6中被引入,此函式返回一個已拒絕的Promise。
Promise.reject('error');
相當於以下程式碼:
new Promise((resolve, reject) => {
reject('error');
});
Promise.resolve
Promise.resolve函式在ES6中被引入,此函式返回一個解決的Promise。
Promise.resolve('success');
相當於以下程式碼:
new Promise((resolve, reject) => {
resolve('success');
});