Promise 源码:异步执行 resolve

Promise 源码:异步执行 resolve

技术杂谈小彩虹2021-07-16 5:33:1960A+A-

前言

解读了同步执行 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 数组里的回调函数取出执行。

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们