跳轉到

A little about Promise

使用時機

當前端同時有數件非同步的工作,且每一件都依賴前一件的結果時,如果使用 CallBack 往往導致結構複雜且不易維護 (也就是所謂的 Callback Hell):

    asyncA(function(dataA) {
        asyncB(dataA, function(dataB) {
            asyncC(dataB, function() {
                ...
            })
        })
    });

因此,我們需要一個機制來簡化上述的結構,並提供一個統一處理錯誤的方法。

簡介

一個 Promise 物件有三種狀態 pendingfullfilledrejected:首先,物件為 pending 狀態,根據執行情形可能會轉變為 fullfilledrejected 兩種狀態;當物件轉換為 fullfilledrejected 狀態後,這個 Promise 必定不會轉變為其他狀態,且有個不能更動的狀態值。

觀念釐清

Promiseasyncawait 的區別

Promise 是「代表非同步結果」的物件本身,而 asyncawait 是建立在 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、鏡像)