A little about Promise
使用時機¶
當前端同時有數件非同步的工作,且每一件都依賴前一件的結果時,如果使用 CallBack 往往導致結構複雜且不易維護 (也就是所謂的 Callback Hell):
asyncA(function(dataA) {
asyncB(dataA, function(dataB) {
asyncC(dataB, function() {
...
})
})
});
因此,我們需要一個機制來簡化上述的結構,並提供一個統一處理錯誤的方法。
簡介¶
一個 Promise 物件有三種狀態 pending , fullfilled , rejected:首先,物件為 pending 狀態,根據執行情形可能會轉變為 fullfilled 或 rejected 兩種狀態;當物件轉換為 fullfilled 或 rejected 狀態後,這個 Promise 必定不會轉變為其他狀態,且有個不能更動的狀態值。
觀念釐清¶
Promise 和 async、await 的區別¶
Promise 是「代表非同步結果」的物件本身,而 async 、 await 是建立在 Promise 之上的語法糖,目的是讓非同步程式碼讀起來像同步:
async函式必定回傳一個 Promise;函式內return value等同於Promise.resolve(value)、throw err等同於Promise.reject(err)await會暫停該 async 函式直到 Promise settle 後取出值,但不阻塞外層 event loop- 錯誤處理:Promise chain 用
.catch(),async/await 用try/catch
// Promise chain
fetchUser(id)
.then(user => fetchPosts(user.id))
.then(posts => render(posts))
.catch(err => console.error(err));
// async/await:邏輯相同,閱讀順序與同步程式一致
async function loadUserPosts(id) {
try {
const user = await fetchUser(id);
const posts = await fetchPosts(user.id);
render(posts);
} catch (err) {
console.error(err);
}
}
開發上的使用建議:
- 線性、需要依序取值的流程 →
async/await較好讀 - 多件互不依賴的任務需並行 → 用
Promise.all([...])表達意圖最直接,不要無謂地一個個await
Promise 和 Ajax call 的區別¶
兩者根本不在同一個層級,會被混淆是因為現代 fetch 回傳的就是 Promise。換言之,Ajax 可以用 Promise 表達,但 Promise 並不只能用在 Ajax。
- Ajax (Asynchronous JavaScript And XML) 是一種技術手法:在背景與伺服器交換資料而不重新整頁
- Promise 是一種非同步流程的抽象,可以包裝任何非同步操作,並不限於 HTTP 請求
// 傳統 Ajax:XMLHttpRequest + callback
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users');
xhr.onload = () => console.log(xhr.responseText);
xhr.send();
// 同樣是 Ajax,但用 fetch + Promise
fetch('/api/users')
.then(res => res.json())
.then(data => console.log(data));
// Promise 不限於網路請求
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
await wait(1000); // 等一秒,沒有任何 HTTP 行為
Promise Combinators¶
處理多個 Promise 時,挑對組合子比手刻迴圈乾淨許多:
Promise.all([...]):全部 fulfill 才繼續,任一 reject 則整體 reject,經典「並行抓多份資源」的工具Promise.allSettled([...]):不論成敗都等齊,回傳每個 Promise 的{status, value | reason},適合「反正都要知道結果」的批次任務Promise.race([...]):最早 settle 的贏(無論 fulfill 或 reject),常用於 timeout 競賽Promise.any([...]):最早 fulfill 的贏;全部 reject 才會走 catch,並拿到一個AggregateError,適合「多個來源擇一」(CDN fallback、鏡像)