Promise 源码:异步执行 resolve

前言

解读了同步执行 resolve 的代码,接下来要看的则是异步执行 resolve了。异步总会比同步复杂得多,它不会按照顺序执行,所以代码会跳来跳去地阅读。

与同步不同的是,异步时代码有可能会先执行 then 函数,将 then 的回调函数保存起来,等到执行 resolve 的时候,再将其取出执行。

new Promise(function (resolve) {
  setTimeout(function () {
    resolve(1);
  }, 1000);
}.then(function (val) {
  console.log(val);
});
复制代码

注:本次阅读的是 then/promise 的 3.0.0 版本,源码请戳 这里

解读

这一次的解读会按照以下的一个执行顺序来进行:

constructor -> fn --异步--> then -> resolve(reject) -> then 回调
复制代码

then

从 fn 函数执行后开始,这时来到了 then 函数:

this.then = function(onFulfilled, onRejected) {
  return new Promise(function(resolve, reject) {
    handle({ onFulfilled: onFulfilled, onRejected: onRejected, resolve: resolve, reject: reject })
  })
}
复制代码

同样先不用管返回的 Promise 实例和它的参数,我们只需要知道 onFulfilled 和 onRejected 被作为参数传递给了 handle 函数。可以把以上代码简化成以下:

this.then = function(onFulfilled, onRejected) {
  handle({ onFulfilled: onFulfilled, onRejected: onRejected })
}
复制代码

handle

来看看 handle 函数是怎么处理 onFulfilled 函数(即 then 的回调函数)的。简化了许多代码:

function handle(deferred) {
  if (state === null) {
    deferreds.push(deferred)
    return
  }
}
复制代码

state 是构造函数里定义的一个变量,它主要作用是用来记录状态,执行 resolve 成功赋值 true,执行 reject 成功赋值 false,初始为 null。

所以此时还没调用 resolve(reject),state 就为 null。这时就用另一个变量 deferreds 来保存 handle 传递进来的参数,即 then 的回调函数。

deferreds 是一个数组,由于我们可以多次调用 then 函数,所以需要一个数组来保存那些回调函数。类似于这样的:

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

p.then(function (val) {
  console.log(val);
});

p.then(function (val) {
  console.log(val + 1);
});
复制代码

至此,then 的回调函数已经被保存起来了,就等着异步执行完毕后 resolve 被调用了。

resolve

假设异步执行完毕了,开始调用 resolve 函数。

function resolve(newValue) {
  resolve_(newValue)
}

function resolve_(newValue) {
  if (state !== null)
    return
  try {
    state = true
    value = newValue
    finale()
  } catch (e) { reject_(e) }
}
复制代码

同样简化了一下代码,去掉暂时不用到的。state 保存 resolve 后的状态,value 保存 resolve 的参数。然后调用 finale 函数。

finale

来到 finale 函数,它将取出之前 deferreds 数组保存的 then 回调函数,再传给 handle 函数,让 handle 函数来执行。

function finale() {
  for (var i = 0, len = deferreds.length; i < len; i++)
    handle(deferreds[i])
  deferreds = null
}
复制代码

handle

好了,又回到了 handle 函数,这一次代码跟刚刚的不一样了。以下代码展示忽略 deferred.resolve 和 deferred.reject 的调用,我们的关注点在于 onFulfilled

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)
  })
}
复制代码

嗯,是的,这一步跟同步执行 resolve 的最后一步是一样的。说实话这里的 nextTick 我也还没搞懂为啥要用,感觉去掉也不影响使用。

不过不影响整体的阅读,现在知道这次调用 handle 是为了调用 onFulfilled 函数,即 then 的回调函数被执行了。

handle 函数的完整代码是这样的:

function handle(deferred) {
  if (state === null) {
    deferreds.push(deferred)
    return
  }
  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)
  })
}
复制代码

代码执行了两次 handle 函数,它有两个用处。在 state 为 null 时,它用来保存回调函数 onFulfilled。再次调用它时,resolve 已被执行,state 被修改成 true,则用来执行 onFulfilled。

总结

Promise 异步执行代码时会比较的绕:

new Promise(function (resolve) {
  setTimeout(function () {
    resolve(1);
  }, 1000);
}.then(function (val) {
  console.log(val);
});
复制代码

执行的顺序回顾一下:

constructor -> fn --异步--> then -> resolve(reject) -> then 回调
复制代码

执行代码时,会先执行构造函数,然后是传入构造函数的 fn 函数。紧接着是执行 then 函数,将其回调函数 onFulfilled 保存在 deferreds 数组中。

等到 fn 函数里的异步代码执行完毕后,调用 resolve 函数,将保存在 deferreds 数组里的回调函数取出执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值