阅读 6458

Promise 源码:then 链式调用

前言

接下来要深入的是 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 的链式调用。

说了上面一堆话,可能有点难理解,来张图配合理解一下吧。

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 回调。

如果有点难理解,莫着急,重新多看几遍文章理解一下。

文章分类
前端