学习手写Promise

535 阅读2分钟

手写promise

使用ES6中class来手写promise

根据本文进行学习总结:BAT前端经典面试问题:史上最最最详细的手写Promise教程 - 掘金 (juejin.cn)

github笔记地址:gihub.com

关于.then执行顺序的说明

代码最初是这样的

then(resolveFn,rejectFn) {
  if(this.state == 'fulfilled') {
    resolveFn(this.value)
  }
  if (this.state == 'rejected') {
    rejectFn(this.reason)
  }
}
let p = new Promise((resolve,reject) => {
  resolve(2)
})
p.then(function() {
  console.log("resolve成功后执行")
})

执行顺序为:

  • new Promise(fn)
  • 执行fn()
  • 执行resolve()/reject()改变状态
  • p.then()执行,判断状态,状态变成了fulfilled
  • 输出:resolve成功后执行

这种写法的遇到了一个问题,当这样写的时候,状态没有改变,但是却已经执行到then了,那怎么办呢?

let p = new Promise((resolve,reject) => {
  setTimeout(resolve(2),2000)
})
p.then(function() {
  console.log("resolve成功后执行")
})

所以我们得把回调函数先存储起来,等到定时器触发之后,在resolve中去执行

代码大概为

let resolve = value => {
  if (this.state === 'pending') {
    this.state = 'fulfilled';
    this.value = value;
    // 一旦resolve执行,调用成功数组的函数
    this.onResolvedCallbacks.forEach(fn=>fn());
  }
}
......
// then中
// 当状态state为pending时
if (this.state === 'pending') {
  // onFulfilled传入到成功数组
  this.onResolvedCallbacks.push(()=>{
    onFulfilled(this.value);
  })
  // onRejected传入到失败数组
  this.onRejectedCallbacks.push(()=>{
    onRejected(this.reason);
  })
}

小结:之前一直以为当resolve/reject后才能执行then,按照这个代码逻辑,其实在p.then()的时候,即使状态还没确定,也已经执行then了,只不过先将回调函数存储起来,到状态确定的时候才执行,所以更准确的应该说,在状态还未确定时,不执行then中的函数,而不是不执行then

then的链式调用

如何实现链式调用?我们在then中返回一个新的promise实例,将执行结果作为参数,传到resolve/reject中,这样在新的promise中我们将判断条件和resolve(value)/reject(err)逻辑写好,等着之后.then就行了

而由于执行结果的不同,我们要对其进行不一样的处理,我们定义resolvePromise进行这一步处理,处理的规则有下面几条

  • 若循环引用,则报“循环引用”错误
  • x 不能是null
  • x 是普通值 直接resolve(x)
  • x 是对象或者函数(包括promise),let then = x.then
  • 如果then是个函数,则用call执行then,第一个参数是this,后面是成功的回调和失败的回调
  • 如果成功的回调还是pormise,就递归继续解析

总结:除了执行结果是promise对象外,其余结果全部直接返回。我们把object/function选出来,然后在里面判断x.then是不是函数,如果是的,我们把他当做一个promise实例继续解析,如果不是,那就是普通对象,将其直接resolve