ESCMScript6(3)Promise對象

1. Promise的含義

Promise 是非同步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。

所謂Promise,簡單說就是一個容器,裡面保存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取非同步操作的消息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理。
 

Promise對象有以下兩個特點

  1. 對象的狀態不受外界影響。Promise對象代表一個非同步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是「承諾」,表示其他手段無法改變。

  2. 一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

注意,為了行文方便,本章後面的resolved統一隻指fulfilled狀態,不包含rejected狀態。

有了Promise對象,就可以將非同步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的介面,使得控制非同步操作更加容易。
 

Promise的缺點

Promise也有一些缺點。
首先,無法取消Promise,一旦新建它就會立即執行,無法中途取消。
其次,如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
第三,當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

如果某些事件不斷地反覆發生,一般來說,使用 Stream 模式是比部署Promise更好的選擇。
 

2.基本用法

一個promise可以通過promise構造函數來創建,這個構造函數只接受一個參數:包含初始化promise程式碼的執行器(executor)函數,在該函數內包含需要非同步執行的程式碼。執行器函數接受兩個參數,分別是resolve函數和reject函數,這兩個函數由JavaScript引擎提供,不需要我們自己編寫。非同步操作結束成功時調用resolve函數,失敗時調用reject函數。

示例程式碼如下:

const promise = new Promise((resolve, reject) => {
    // 開啟非同步操作
    setTimeout(function() {
      try {
        let c = 6 / 2
        // 執行成功時調用resolve函數
        resolve(c)
      } catch (ex) {
        // 執行失敗時調用reject函數
        reject(ex)
      }
    }, 1000)
  })

  在執行器函數內包含了非同步調用,在1s後執行兩個數的除法運算,如果成功,則用相除的結果作為參數調用resolve函數,失敗則調用reject函數。
  每個promise都會經歷一個短暫的生命周期:先是出於進行中(pending)的狀態,此時操作尚未完成,所以它是未處理的,一旦非同步操作執行結束,promise則變為已處理的狀態。操作結束後,根據非同步操作執行成功與否,可以進入以下兩個狀態之一:
  (1)fulfilled:promise非同步操作成功完成
  (2)rejected:由於程式錯誤或者其他的一些原因,promise非同步操作未能成功完成,即已失敗。
  一旦promise狀態改變,就不會再變,任何時候都可以得到這個結果。promise對象的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。
  在promise狀態改變後,我們怎麼去根據不同的狀態來做相應的處理呢?promise對象有一個then()方法,它接受2個參數:第一個是當promise的狀態變為fulfilled時調用的函數,與非同步操作相關的附加數據通過調用resolve函數產地給這個完成函數;第二個是當promise的狀態變為rejected時要調用的函數,所有與失敗相關的附加數據通過調用rejected函數傳遞個這個拒絕函數。添加promise的then()方法的調用,程式碼如下:

promise.then(value => {
    console.log(value);  // 3
  }, err => {
    console.error(err.message);
  })

  then()方法的兩個參數都是可選的。例如,只在執行失敗後進行處理,可以給then()方法的第一個參數出傳遞null。程式碼如下:

promise.then(null, err => {
    console.error(err.message);
  })

  promise對象還有一個catch()方法,用於在執行失敗後進行處理,等價於上述只給then()方法傳入拒絕處理函數的程式碼,如下:

promise.catch(err => {
    console.error(err.message)
  })

  但是通常我們是將then()方法和catch()方法一起使用來對非同步操作的結果進行處理,這樣能更清楚的指明操作結果是成功還是失敗,程式碼如下:

promise.then(value => {
    // 完成
    console.log(value);
  }).catch(err => {
    // 拒絕
    console.error(err.message);
  })