JavaScript

理解 Promise 狀態及使用方式

陳志澤 2017/12/14 15:31:15
758

理解 Promise 狀態及使用方式


簡介

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

作者

陳志澤


 

前言


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

 

什麼是 Promise ?


 
Promise 是 ES6 訂定的新標準物件,但其實這也不是什麼新鮮事,因為早在訂定此標準前,已有許多函式庫或框架實作出相同概念的做法; Promise 語法結構可滿足開發者解決非同步操作的需求,透過此流程語法結構,提供一個比較方便的操作方式來調用非同步作業,使用 Promise 表示一個非同步作業的最終結果,在語法使用上較傳統 call back 方式清晰好理解。

 

ES6 Promise 支援性


 
ES6 Promise 現今已經滿足絕大部分(89%)主流瀏覽器的支持,如果需要在舊瀏覽器中使用 Promise 來操作非同步作業,除使用各函式庫自行依照 Promise 概念實作出的語法外,也可以使用 babel-pollyfill 讓舊瀏覽器使用 ES6 新語法。

 

 

建立 Promise 物件


建立 Promise 方式如下

var promise = new Promise ( (resolvereject) => { ... } )

  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( onFulfilledonRejected )

  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( [promise1promise2 ....] ).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"

 

陳志澤