Promise深入探索

zifeiyu發表於2018-12-05

Promis.resolve()

  1. 如果向Promise.resolve()傳遞一個非promise非thenalbe的立即值,就會得到一個用這個值填充的promise。
  2. 如果向Promise.resolve()傳遞一個promise,就只會返回同一個promise
  3. 如果向Promise.resolve()傳遞了一個非Promise的thenable值,前者會試圖展開這個值,而且展開過程中會持續到提取出一個具體的非類Promise的最終值。

假設我們要呼叫一個工具 foo(..) ,且並不確定得到的返回值是否是一個可信任的行為良好的 Promise,但我們可以知道它至少是一個 thenable。Promise.resolve(..) 提供了可信任的 Promise 封裝工具

// 不要只是這麼做:
foo( 42 )
.then( function(v){
    console.log( v );
} );

// 而要這麼做:
Promise.resolve( foo( 42 ) )
.then( function(v){
    console.log( v );
} );
複製程式碼

對於用 Promise.resolve(..) 為所有函式的返回值(不管是不是 thenable)都封裝一層。另一個好處是,這樣做很容易把函式呼叫規範為定義良好的非同步任務。如果 foo(42) 有時會返回一個立即值,有時會返回 Promise,那麼 Promise.resolve( foo(42) ) 就能夠保證總會返回一個 Promise 結果

預設錯誤處理函式

如果一個promise鏈中的上一級promise丟擲一個錯誤,但是下一級promise並沒有配置錯誤處理函式,promise會有一個預設處理函式來處理這個錯誤,即吧這個錯誤重新丟擲給下一級promise的錯誤處理函式。

如果一直找到最後一級的promise都沒有錯誤處理函式,那麼promise會在控制檯打出一個錯誤資訊,並且這個錯誤是有可能中斷nodejs程式的。

DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
複製程式碼

(在瀏覽器環境貌似並不會中斷程式)

Promise鏈

  • 呼叫promise的then方法,會自建立一個新的Promise,並且決議的值就是then中resolve/reject函式的返回值;
  • 如果resolve/reject函式返回的是一個Promise,那麼會等待這個Promise的決議結果,並將決議結果作為當前then方法生成的Promise的決議值;
var a = new Promise(function(resolve, reject) {
  resolve(10)
}).then(
  res => {
    return b
  }
).then(res => {
  console.log('1err:' + res)
  return res
}).then(res => {
  console.log('1res:' + res)
  return res
}, err => {
  console.log('2err:' + err)
})

var b = new Promise(function(resolve, reject) {
  resolve(123123123123)
})
複製程式碼

Promise構造器中兩個回撥函式

reject很好理解就是處理錯誤,那麼resolve為什麼不用fulfill呢?貌似應該是處理成功請求?實際上resolve的意思是決議,也就是可能是成功的結果也可能是失敗的結果;比如所給resolve傳入thenable或者一個真正的Promise,這個時候決議的結果取決於返回值的決議結果,所以說resolve返回的有可能是成功也有可能是失敗;

var rejectedPr = new Promise( function(resolve,reject){
    // 用一個被拒絕的promise完成這個promise
    resolve( Promise.reject( "Oops" ) );
} );

rejectedPr.then(
    function fulfilled(){
        // 永遠不會到達這裡
    },
    function rejected(err){
        console.log( err ); // "Oops"
    }
);
複製程式碼

前面提到的 reject(..) 不會像 resolve(..) 一樣進行展開。如果向 reject(..) 傳入一個 Promise/thenable 值,它會把這個值原封不動地設定為拒絕理由。後續的拒絕處理函式接收到的是你實際傳給 reject(..) 的那個 Promise/thenable,而不是其底層的立即值。

回撥函式簡介

回撥函式就是一個通過函式指標呼叫的函式。如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由該函式的實現方直接呼叫,而是在特定的事件或條件發生時由另外的一方呼叫的,用於對該事件或條件進行響應

而then方法的兩個回撥函式應該使用onFulfilles/fulfilled和onRejected/rejected因為對於then來說第一個回撥只能處理正確的結果。

(fulfilled the promis 兌現承諾)

Promise錯誤處理函式的細節

通常我們有兩種寫法來捕獲錯誤:

var a = new promise(function(resolve, reject){})

// 在then方法的內部第二個回撥捕獲
a.then(res => {
    
}, err => {
    
})

// 在catch方法中捕獲
a.then(res => {}).catch(err => {})

複製程式碼

那麼這兩種方法有什麼不同? 實際上第二種方法是包含第一種方法的,只不過then方法中的第二個回撥被預設執行了,而預設的錯誤處理回撥只是簡單的丟擲錯誤,然後就會被catch方法捕獲; 這裡就有一個問題,如果a的決議是resolve,那麼就會呼叫then的第一個成功回撥,如果這個成功回撥中的具體程式碼中發生了錯誤,第一種方法是無法捕獲的,而第二種方法是可以捕獲的。

var a = new promise(function(resolve, reject){
    resolve(12)
})

// 在then方法的內部第二個回撥捕獲
a.then(res => {
    aaa() // 這裡aaa是不存在的會發生錯誤
}, err => {
    console.log(err) // 不會走這裡,所以錯誤不會被捕獲
})

// 在catch方法中捕獲
a.then(res => {
    aaa() // 這裡aaa是不存在的會發生錯誤
}).catch(err => {
    console.log(err) // 這裡會捕獲then方法中的所有錯誤,自然也能捕獲aaa導致的錯誤
})
複製程式碼

Promise.race()的應用場景

實際上Promise.race([p1, p1, p3])這種模式在程式設計中通常是一種被稱為競態的bug,但是它還是有一定的應用場景:

  1. 請求超時處理
var p1 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve(2222)
  }, 2000)
})
// 定義一個超時函式,如果p1超過這個時間還未決議,就認為這個請求超時了。
var timeoutPromise = new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('timeout')
  }, 1000)
})

Promise.race([p1, timeoutPromise]).then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})
複製程式碼

有時候我們可能會想,Promsie.race()中那些別丟棄(比賽中失敗)的Promise哪裡去了?當然最後會被垃圾回收,但是,Promsie不能別停止,它們依舊會完成自己的任務直到決議,目前來看只是被默默的丟棄了,像空氣一樣,或許在以後的規範中會新增類似與finally之類的回撥函式用於處理這些被遺棄的孩子。

相關文章