[JS] 理解 Promise 狀態及使用方式

在做中學的過程中,遇到 Promise 物件總是 then 來 then 去,雖然大致了解作法及應用方式,但是一直沒有時間停下腳步仔細看看,內心覺得有點不踏實;因此最近花了一點時間看看文章,寫了一下筆記希望能幫助到需要的人。

前言


當今作為 Http Client 套件 ( jQuery, axios, fetch ... ) 多數已支援 Promise 標準,開發人員可透過 Promise 物件掌控非同步作業的執行狀態,依據 Promise 狀態決定成功 / 失敗時所需執行的行為,在語法使用上較傳統 call back 方式清晰好理解。本篇文章針對 Promise 物件的操作方式進行介紹,對 Promise 有初步的認識後,在操作上比較不會迷失在 then 來 then 去的 promise chain 中。

 

建立 Promise 物件


建立 Promise 方式如下

var promise = new Promise ( (resolve, reject) => { ... } )

  1. 在建構函式傳入 executor 函式
  2. 可在函式中執行非同步作業
  3. 當作業順利完成後:
    1. 呼叫 resolve(value) 回應所得資料
    2. value 通常是合法的 javascript 值
    3. 會進入 Fulfilled (已實現) 狀態
  4. 當作業發生錯誤時:
    1. 呼叫 reject(reason) 回應錯誤資訊
    2. reason 通常會式 Error 物件
    3. 會進入 Rejected (已拒絕) 狀態

範例如下:

function doSomethingAsync(){
  return new Promise((resolve, reject) => {

    // 模擬非同步作業(ex. call api)
    setTimeout(function () {
      let isSuccess = false
      if (isSuccess) {
        // 成功(資料 value 向下一個連鎖傳遞)
        resolve('success')
      } else {
        // 失敗(錯誤 reason 向下一個連鎖傳遞)
        reject(new Error('something wrong'))
      }
    }, 2000)

  })
}

 

 

 依據 promise 狀態來決定處置方式


可使用 then 獲得最終處理結果及進行接續行為

var newPromise = promise.then( onFulfilled, onRejected )

  1. 傳入 onFulfilled 方法來定義作業成功時的處理方式
  2. 傳入 onRejected 方法來定義作業失敗時的處理方式
  3. 執行後會另外產生新的 Promise 物件
    (因此可以使用 chained 結構繼續操作 promise)

 

onFulfilled = (value) => {...}

  1. 在 Promise 的 executor 中呼叫 resolve 方法會進入此方法中
    ( 狀態為 Fulfilled 已實現時 )
  2. 傳入值:
    1. 由上層 executor 透過 resolved(value) 傳入的資料
    2. 或從上層 onFulfilled 或 onRejected 方法直接 return value
    3. 不區分值是從 onFulfilled 還是 onRejected 傳出來 (因在 onFulfilled 或 onRejected 不發生錯誤 / 不呼叫 reject 時,都會進入下個 onFulfilled 中,當然也包括回傳值)
  3. 回傳值:
    1. value 值 (可以從新Promise 物件 then 中 onFulfilled 方法參數中取得)
    2. Promise 物件
      1. 當有需要再處理一個非同步作業時使用
      2. 自己建立一個 Promise 取代 then 回傳的新 Promise 物件
    3. thenable 物件

 

onRejected = (reason) => {...}

  1. 在 Promise 的 executor 中呼叫 reject方法會進入此方法中
    ( 狀態為 Rejected 已拒絕時 )
  2. 傳入值:
    1. 由上層 executor 透過 reject(reason) 傳入的錯誤發生資訊
    2. 或從上層 onFulfilled 或 onRejected 方法直接呼叫 Promise.reject(reason) 
  3. 回傳值:也是可以回傳 value, promise 或 thenable 物件
  4. 多數使用情境下在 then 中只會定義 onFulfilled 方法 (會忽略此部分)
  5. 錯誤多會交由 promse.catch 作攔截處置

 

範例如下:

// promise 成功要執行的方法
function onFulfilled (value) {
  console.log(value)  // 'success'
}

// promise 失敗要執行的方法
function onRejected (reason) {
  console.log(reason.message)  // 'something wrong'
}

// then 就是處置 promise 成功或失敗要做的事情
doSomethingAsync()
  .then(onFulfilled, onRejected) 

// 通常不特別在 then 中定義 onRejected 方法
// 多數使用 catch 直接捕捉/處理上方連鎖發生的錯誤   
doSomethingAsync()
  .then(onFulfilled) 
  .catch(onRejected) // 等同 then(undefined, onRejected)

 

 

 執行等待多個 promise 皆處理完畢 ( 靜態方法 all )


使用 all 執行陣列中的所有 promise 物件

Promise.all( [promise1, promise2 ....] ).then( onFulfilled ). catch( onReject )

  1. 所有 promise 都 resolve 後才會進入 onFulfilled
    (onFulfilled 中傳入的 value 為陣列格式,保存各 promise 回傳的值)
  2. 陣列順序與執行順序無關
  3. 所有 promise 其中任一發生錯誤或 reject 情況則馬上回傳

 

範例如下:

var promise1 = new Promise((resolve)=>{
  setTimeout(()=>{
    resolve('take 1 second')
  },1000)
})

var promise2 = new Promise((resolve)=>{
  setTimeout(()=>{
    resolve('take 2 second')
  },2000)
})

Promise.all([promise1, promise2])
  .then((values)=>{
    console.log(values) // ['take 1 second', 'take 2 second']
  })
  .catch((error)=>{
     console.log(error)
  })

 

 

建立已具狀態的 promise 物件 ( 靜態方法 resolve 與 reject )


直接產生  Fulfilled 已實現狀態的 promise 物件 ( 只會進入 onFulfilled )

Promise.resolve( value ).then(onFulfilled, onRejected)

 

直接產生 Rejected 已拒絕狀態的 promise 物件 ( 只會進入 onRejected )

Promise.reject( reason ).then(onFulfilled, onRejected)

 

// promise 成功要執行的方法
function onFulfilled (value) {
  console.log(value)  
}

// promise 失敗要執行的方法
function onRejected (reason) {
  console.log(reason.message) 
}

Promise.resolve('success').then(onFulfilled, onRejected)
// go onFulfilled and show "success"

Promise.reject(new Error('fail')).then(onFulfilled, onRejected)
// go onRejected and show "fail"

 

 

參考資訊


從Promise開始的JavaScript異步生活

 


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !