Promise 原始碼:then 鏈式呼叫

cobish發表於2018-06-13

前言

接下來要深入的是 then 鏈式呼叫,這個是實現中最繞的一塊。在解讀之前,我們再加深一下印象。

constructor -> fn --同步--> resolve(reject) -> then -> then 回撥
constructor -> fn --非同步--> then -> resolve(reject) -> then 回撥
複製程式碼

無論是同步還是非同步的情況,then 回撥函式 都會在 resolve 執行之後,才會執行。所以可以這樣理解,只有執行了 resolve 之後,才會觸發 then 回撥函式的執行。

注:本次閱讀的是 then/promise 的 3.0.0 版本,原始碼請戳 這裡

解讀

先來看下怎麼示例程式碼:

var p1 = new Promise(function (resolve) {
  setTimeout(function () {
    resolve(1);
  }, 1000);
})

p1.then(function (val) {
  var p3 = new Promise(function (resolve) {
    setTimeout(function () {
      resolve(val + 1);
    }, 1000);
  });

  return p3;
}).then(function (val) {
  console.log(val);
});
複製程式碼

看到上面的程式碼,我們能顯示地看到 new 了兩個 Promise 例項,分別用 p1p3 表示。然後再從之前 then 方法的實現中,我們會看到它也 return 了一個 Promise 例項,我們用 p2 來表示 then 函式執行後返回的 Promise 例項,以便接下來的區分。

this.then = function(onFulfilled, onRejected) {
  return new Promise(function(resolve, reject) {
    handle({ onFulfilled: onFulfilled, onRejected: onRejected, resolve: resolve, reject: reject })
  })
}
複製程式碼

對著上面的程式碼,我們再看下變數分別表示著啥:

  • p1:最外層的 Promise 例項;
  • p2:p1 呼叫 then 函式返回的 Promise 例項;
  • p3:p1 的 then 回撥函式返回的 Promise 例項。

執行順序

理一下上面示例程式碼的執行順序:

  1. new 了一個 Promise 例項賦值給 p1,並執行 fn 函式;
  2. p1 呼叫它的 then 函式,返回 p2;
  3. p2 呼叫它的 then 函式(實際上又返回一個新的 Promise 例項);
  4. 一秒後,執行 p1 的 resolve 函式;
  5. 觸發 p1 的 then 回撥函式,此時 p3 被建立並返回;
  6. 再一秒後,p3 執行 resolve 函式;
  7. 觸發 p2 的 then 回撥函式。

疑惑:這裡 p3 執行了它的 resolve 函式,怎麼會觸發 p2 的 then 回撥函式,不應該是觸發自己的 then 回撥函式的嗎??

handle

從第 5 步開始看程式碼,觸發 p1 的 then 回撥函式,也就會觸發 p1 裡的 handle 函式:

function handle(deferred) {
  nextTick(function() {
    var cb = state ? deferred.onFulfilled : deferred.onRejected
    if (typeof cb !== 'function'){
      (state ? deferred.resolve : deferred.reject)(value)
      return
    }
    var ret
    try {
      ret = cb(value)
    }
    catch (e) {
      deferred.reject(e)
      return
    }
    deferred.resolve(ret)
  })
}
複製程式碼

首先會執行 onFulfilled 函式,即 p1 的 then 回撥函式,此時建立 p3 返回賦值給 ret。然後再呼叫 deferred 的 resolve,其實是呼叫了 p2 的 resolve,將 p3 作為引數傳入。

resolve

deferred 呼叫 resolve,將不會執行的程式碼先去掉:

function resolve(newValue) {
  if (delegating)
    return
  resolve_(newValue)
}

function resolve_(newValue) {
  if (state !== null)
    return
  try {
    if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.')
    if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
      var then = newValue.then
      if (typeof then === 'function') {
        delegating = true
        then.call(newValue, resolve_, reject_)
        return
      }
    }
  } catch (e) { reject_(e) }
}
複製程式碼

因為 newValue 就是 p3,判斷它是一個帶有 then 的物件後,會呼叫它的 then 函式,將 p2 的 resolve_ 作為 p3 的 then 回撥函式傳入。

如果 p3 執行了 resolve,即會執行它的 then 回撥函式,即 p2 的 resolve_。如果 p2 執行了 resolve_,即會執行自己的 then 回撥函式。

這裡可以把程式碼視為這樣,resolve_ 裡的變數都是 p2 的變數:

then.call(newValue, function resolve_ (newValue) {
  // 更新狀態
  state = true

  // 記住 resolve 的引數
  value = newValue
  finale()
}, reject_);
複製程式碼

p2 的 then 回撥函式,就是示例程式碼裡那個鏈式呼叫的 then 的回撥函式。所以就實現了 then 的鏈式呼叫。

說了上面一堆話,可能有點難理解,來張圖配合理解一下吧。

Promise 原始碼:then 鏈式呼叫

delegating

簡單講一下變數 delegating,因為 p2 的 resolve_ 可以通過呼叫自己 resolve 來觸發,也可以通過 p3 的 then 回撥來觸發。當通過 p3 的 then 回撥來觸發時,delegating 將設定為 true,此時就切斷了 p2 通過呼叫自己 resolve 來觸發的情況。

function resolve(newValue) {
  if (delegating)
    return
  resolve_(newValue)
}
複製程式碼

nextTick

在知道了整個 then 鏈式呼叫的順序之後,最後來看看 nextTick。以我的理解,使用 nextTick 是為了讓 then 回撥函式在所以的 then 程式碼執行後才會執行。

否則,以下程式碼,p1 回撥函式先執行,再執行第二個 then 函式,這就違背了 ES6 中 Promise 是微觀任務佇列的概念了。

var p1 = new Promise(function (resolve) {
  resolve(1);
})

p1.then(function (val) {
  var p3 = new Promise(function (resolve) {
    resolve(val + 1);
  });

  return p3;
}).then(function (val) {
  console.log(val);
});
複製程式碼

總結

Promise 的 then 鏈式呼叫,簡單來說就是用一個新的 Promise 例項來做橋樑,當前面 then 回撥函式裡返回的 Promise 執行 resolve,就會觸發橋樑去執行它的 then 回撥。

如果有點難理解,莫著急,重新多看幾遍文章理解一下。

相關文章