promise到底是怎么实现的

590 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

DOM/BOM API 中新加入的 API 大多数都是建立在 Promise 上的,而且新的前端框架也使用了大量的 Promise。可以这么说,Promise 已经成为现代前端的"水"和"电",很是关键,所以深入了解 Promise 势在必行。

我们知道promise的出现主要是为了解决回调地域的问题,让代码更清晰,但是它是如何解决回调地狱的呢?

回调函数的延时绑定

function executor(resolve, reject) {
  resolve(100)
}
let demo = new Promise(executor)
function onResolve(value){
  console.log(value)
}
demo.then(onResolve)

先看看它的执行顺序,首先执行 new Promise 时,Promise 的构造函数会被执行,不过由于 Promise 是 V8 引擎提供的,所以暂时看不到 Promise 构造函数的细节。

接下来,Promise 的构造函数会调用 Promise 的参数 executor 函数。然后在 executor 中执行了 resolve,resolve 函数也是在 V8 内部实现的,那么 resolve 函数到底做了什么呢?我们知道,执行 resolve 函数,会触发 demo.then 设置的回调函数 onResolve,所以可以推测,resolve 函数内部调用了通过 demo.then 设置的 onResolve 函数

由于 Promise 采用了回调函数延迟绑定技术,所以在执行 resolve 函数的时候,回调函数还没有绑定,那么只能推迟回调函数的执行。

这样按顺序陈述可能不太清楚,下面来模拟实现一个 Promise,会实现它的构造函数、resolve 方法以及 then 方法,以方便看清楚 Promise 的背后都发生了什么:

function Bromise(executor) {
  var onResolve_ = null
  var onReject_ = null
   //模拟实现resolve和then,暂不支持rejcet
  this.then = function (onResolve, onReject) {
      onResolve_ = onResolve
  };
  function resolve(value) {
       onResolve_(value)
  }
  executor(resolve, null);
}

利用上面的Bromise来实现业务代码:

function executor(resolve, reject) {
  resolve(100)
}
//将Promise改成我们自己的Bromsie
let demo = new Bromise(executor)
function onResolve(value){
  console.log(value)
}
demo.then(onResolve)

执行这段代码,我们发现执行出错,报错内容是Uncaught TypeError: onResolve_ is not a function,之所以出现这个错误,是由于 Bromise 的延迟绑定导致的,在调用到 onResolve_ 函数的时候,Bromise.then 还没有执行,所以执行上述代码的时候,当然会报onResolve_ is not a function的错误了。也正是因为此,我们要改造 Bromise 中的 resolve 方法,让 resolve 延迟调用 onResolve_。

要让 resolve 中的 onResolve_ 函数延后执行,可以在 resolve 函数里面加上一个定时器,让其延时执行 onResolve_ 函数,改造后的代码:

function resolve(value) {
  setTimeout(()=>{
      onResolve_(value)
  },0)
}

上面采用了定时器来推迟 onResolve 的执行,这样就实现了Bromise回调函数的延时绑定技术。

Promise 与微任务

上面使用了定时器实现了回调函数的延时绑定技术,但是定时器属于宏任务,宏任务的效率并不是太高。好在我们有微任务,所以 Promise 内部又把这个定时器改造成了微任务了(这是在V8内部实现的),这样既可以让 onResolve_ 延时被调用,又提升了代码的执行效率。这就是Promise属于微任务的原由了。

在执行resolve函数的时候,在这个时候then里面的回调函数的执行时机放在微任务队列中,当执行完resolve里面的逻辑后,就开始执行then里面的回调函数。

手写一个Promise

通过上面的知识,我们应该对手写Promise有了一个大概的轮廓,下面我们来手写一个promise。

executor 与三种状态

一个 Promise 应该具备的最基本的特征,至少有以下两点:

  • 可以接收一个 executor 作为入参
  • 具备 pending、resolved 和 rejected 这三种状态
function Bromise(executor) {
  // value 记录异步任务成功的执行结果
  this.value = null
  // reason 记录异步任务失败的原因
  this.reason = null
  // status 记录当前状态,初始化是 pending
  this.status = 'pending'

  // 把 this 存下来,后面会用到
  var self = this

  // 定义 resolve 函数
  function resolve(value) {
    // 异步任务成功,把结果赋值给 value
    self.value = value
    // 当前状态切换为 resolved
    self.status = 'resolved'
  }

  // 定义 reject 函数
  function reject(reason) {
    // 异步任务失败,把结果赋值给 value
    self.reason = reason
    // 当前状态切换为 rejected
    self.status = 'rejected'
  }

  // 把 resolve 和 reject 能力赋予执行器
  executor(resolve, reject)
}

then 方法的行为

每一个 promise 实例一定有个 then 方法,因此then 方法应该装在 Promise 构造函数的原型对象上。

// then 方法接收两个函数作为入参(可选)
Bromise.prototype.then = function (onResolved, onRejected) {
  // 注意,onResolved 和 onRejected必须是函数;如果不是,我们此处用一个透传来兜底
  if (typeof onResolved !== 'function') {
    onResolved = function (x) {
      return x
    }
  }
  if (typeof onRejected !== 'function') {
    onRejected = function (e) {
      throw e
    }
  }

  // 依然是保存 this
  var self = this
  // 判断是否是 resolved 状态
  if (self.status === 'resolved') {
    // 如果是 执行对应的处理方法
    onResolved(self.value)
  } else if (self.status === 'rejected') {
    // 若是 rejected 状态,则执行 rejected 对应方法
    onRejected(self.reason)
  }
}

链式调用

几个重大的改造点:

  • then方法中应该直接把 this 给 return 出去(链式调用常规操作);
  • 链式调用允许我们多次调用 then,多个 then 中传入的 onResolved(也叫onFulFilled) 和 onRejected 任务,我们需要把它们维护在一个队列里;
  • 要想办法确保 then 方法执行的时机,务必在 onResolved 队列 和 onRejected 队列批量执行前。不然队列任务批量执行的时候,任务本身都还没收集完,就乌龙了。一个比较容易想到的办法就是把批量执行这个动作包装成异步任务,这样就能确保它一定可以在同步代码之后执行了。
function Bromise(executor) {
  this.value = null
  this.reason = null
  this.status = 'pending'

  // 缓存两个队列,维护 resolved 和 rejected 各自对应的处理函数
  this.onResolvedQueue = []
  this.onRejectedQueue = []

  var self = this

  function resolve(value) {
    // 如果不是 pending 状态,直接返回
    if (self.status !== 'pending') {
      return
    }
    self.value = value
    self.status = 'resolved'
    // 用 setTimeout 延迟队列任务的执行
    setTimeout(function () {
      // 批量执行 resolved 队列里的任务
      self.onResolvedQueue.forEach(resolved => resolved(self.value))
    })
  }

  function reject(reason) {
    // 如果不是 pending 状态,直接返回
    if (self.status !== 'pending') {
      return
    }
    self.reason = reason
    self.status = 'rejected'
    // 用 setTimeout 延迟队列任务的执行
    setTimeout(function () {
      // 批量执行 rejected 队列里的任务
      self.onRejectedQueue.forEach(rejected => rejected(self.reason))
    })
  }

  // 把 resolve 和 reject 能力赋予执行器
  executor(resolve, reject)
}
Bromise.prototype.then = function (onResolved, onRejected) {
  if (typeof onResolved !== 'function') {
    onResolved = function (x) {
      return x
    }
  }
  if (typeof onRejected !== 'function') {
    onRejected = function (e) {
      throw e
    }
  }

  var self = this
  if (self.status === 'resolved') {
    onResolved(self.value)
  } else if (self.status === 'rejected') {
    onRejected(self.reason)
  } else if (self.status === 'pending') {
    self.onResolvedQueue.push(onResolved)
    self.onRejectedQueue.push(onRejected)
  }
  return this
}