iT邦幫忙

2023 iThome 鐵人賽

DAY 4
0
自我挑戰組

JS 加強筆記系列 第 4

Day 04:promise 結構及特性

  • 分享至 

  • xImage
  •  

promise 之所以比 callback 直覺、簡單,是因為它的概念是:

  1. 有需要花點時間的任務 (producing code)。
  2. 有需要拿到 1. 的結果後要做的任務 (consuming code)。
  3. 利用 promise 這個 JS 物件,把 1, 2 連接起來。

也就是 producing code 花時間去產生預期結果,promise 確保讓需要的地方都可以拿到。

executor function

建構 promise 物件:

let promise = new Promise(function(resolve, reject) {
    // 這個函式是 executor (the producing code)
});

new Promise 建構式中傳入的函式稱為 executor,它裡面要放的就是 producing code。當一個新 promise 物件建立時 (建構式回傳 promise 物件前),executor 會立刻自動執行,它的兩個參數 resolvereject 是 JS 提供的 callback,當 producing code 取得結果後,應該呼叫其中一個函式:

  • resolve(value) — 任務成功的時候呼叫,傳入結果的值。
  • reject(error) — 有錯誤時呼叫,傳入錯誤物件。

另外,由建構式回傳的 promise 有幾個內建屬性:

  • state — 初始是 'pending',呼叫 resolve 的話變為 'fulfilled',呼叫 reject 則變為 'rejected'。
  • result — 初始是 undefined,呼叫 resolve 的話變為結果值,呼叫 reject 則變為 error。

以下是任務成功完成的例子,executor 在一秒後呼叫了 resolve,promise 的狀態從 'pending' 變成了 'fulfilled':

let promise = new Promise(function(resolve, reject) {
    setTimeout(() => resolve('done'), 1000);
});

這是失敗時 executor 呼叫 reject 送出錯誤的例子,promise 的狀態從 'pending' 變成了 'rejected':

let promise = new Promise(function(resolve, reject) {
    setTimeout(() => reject(new Error('Whoops!')), 1000);
});

換句話說,executor 內執行可能要花點時間的任務,最終會藉由呼叫 resolvereject 來改變 promise 物件的狀態。

成功 (fulfilled) 或失敗 (rejected) 的 promise 統稱為 settled,代表有確定的結果。settled 只是相對於 pending 的一種表達,並不是正式的狀態。

promise 的一些細節

  1. 只會有一個成功結果或一個錯誤
    executor 只應呼叫一次 resolve reject,狀態改變就成定局,之後再呼叫都會被忽略。此外,resolvereject 也都只能有一個 (或沒有) 參數,多個就被忽略。

    let promise = new Promise(function(resolve, reject) {
        resolve('done');
    
        reject(new Error('…')); // 被忽略
        setTimeout(() => resolve('…')); // 被忽略
    });
    
  2. 錯誤物件
    失敗的情況時,reject 其實可以和 resolve 一樣傳入任何參數,但建議使用 Error 物件。

  3. 可以馬上呼叫 resolvereject 嗎?
    executor 內通常會處理非同步任務,但不一定非得處理非同步,它也可以馬上呼叫 resolvereject

    let promise = new Promise(function(resolve, reject) {
        resolve(123);
    });
    

    這種情況可能用在開始任務前的檢查,若發現已經完成,就可以直接回傳成功的 promise。

    promise 是因為適合所以才拿來處理非同步,而不是它只能放非同步的工作或會把工作變非同步。這聽起來非常簡單合理,但人有時就是會莫名迷失,像我就曾經指著自己寫在 promise 裡的東西問隔壁同事:這到底是同步還是非同步@@ (然後他也答不出來)。

  4. promise 物件的狀態
    上面提到 promise 物件的 stateresult 屬性無法直接調用。要得到 promise 的結果要使用特定方法,這就跟接下來要寫的 consuming code 如何接到結果有關。

關於 resolved 的額外補充

如果上面內容已經讓你的人生很快樂,建議不要讀這段。

MDN 裡面提到,一般口語表達中 resolved promises 和 fulfilled promises 基本上是同樣的意思 (這不難理解,因為在呼叫 resolve 後會得到 fulfilled promise)。但根據這份 States and Fates 文件,所謂 resolved promise 它的狀態也可以是 rejected 或 pending,也就是 resolved 是相對於 unresolved 而言,跟狀態無關,而這裡的 resolved 是指「當對 promise 呼叫 resolvereject 不會改變它的狀態」。例如當 promise 狀態已經是 fulfilled 或 rejected,就像上面說的,再次呼叫也沒有影響。或者像這個例子:

new Promise((resolveOuter) => {
    resolveOuter(
        new Promise((resolveInner) => {
            setTimeout(resolveInner, 1000);
        }),
    );
});

外面的 promise 在建立的時候就已經 resolved,因為 executor 直接同步呼叫了 resolve,但是因為它要等裡面的 promise,所以狀態並不會改變 (仍然是 pending),一秒之後才會變成 fulfilled。


我不知道這段對於理解 promise 會是幫助還是阻礙,不過確實想起剛開始認識 promise 時,對於各種 resolved、fulfilled、settled...的表達感到混亂。所以雖然不同情境下的定義有細微差異讓人很煩躁,還是很高興可以讀到這樣的詳細解釋。


上一篇
Day 03:callbacks 的錯誤處理及問題
下一篇
Day 05:then、catch、finally
系列文
JS 加強筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言