手把手教你一步步写一个Promise(上)

643 阅读7分钟

前言

Promise相信大家都很熟悉,而手写一个Promise也是大厂面试的热点,那么让我们一步步的手写一个Promise吧

搭建Promise的骨架

根据promise/A+规范,Promsie构造函数返回一个promise对象实例,这个对象具有一个then方法。在then方法中,有两个函数类型的参数,分别是onfulfilled和onrejected。

其中,onfulfilled通过参数可以获取Promise对象经过resolve处理后的值,onrejected可以获取Promise对象经过reject处理后的值。

我们先根据规范把构造函数和then方法写好

function Promise(executor){

}

Promise.prototype.then=function(onfulfilled,onrejected){

}

在使用new调用Promise构造函数时,往往是异步操作结束时调用executor的参数resolve,并将经过resolve处理后的值,可以在后续的then方法的第一个函数参数中第一个函数参数onfulfilled中拿到;当出现错误时,调用executor的参数reject,并将错误信息作为reject的函数参数执行,这个错误信息可以在后续then方法的第二个参数onrejected中得到,代码如下

function Promise(executor){
   this.status = 'pending'
   this.value = null
   this.reason = null
    //箭头函数保证resolve和reject方法能够拿到Promise实例的值
   const resolve=value=>{
       this.value =value
     }
   const  reject=reason=>{
       this.reason = reason
   }
   executor(resolve,reject)
   }
Promise.prototype.then = function(onfulfilled ,onrejected){
	onfulfilled(this.value)
    onrejected(this.error)
   }

完善Promise的状态

我们测试一下之前的代码

let promise = new Promise((resolve,reject) =>{
    resolve('data')
    reject('error')
})
promise.then(data=>{
    console.log(data)
},error =>{
    console.log(error)
})

执行上面代码将会输出data和error,而正常的promise会输出data

因为Promise的实例状态只能从pending变为fulfilled,或者从pending变为rejected。而且状态一旦变更完毕,就不可能再次变化。代码如下

 function Promise(executor) {

     this.status = 'pending'
     this.value = null
     this.reason = null
     //在resovle和reject方法中加入了判断,只允许Promsie实例状态从pending变为fulfilled,或者从pending变为rejected
     const resolve = value => {
         if (this.status === 'pending') {
             this.value = value
             this.status = 'fulfilled'
         }

     }
     const reject = reason => {
         if (this.status === 'pending') {
             this.reason = reason
             this.status = 'rejected'
         }

     }
     executor(resolve, reject)
 }
 Promise.prototype.then = function (onfulfilled , onrejected ) {

     if (this.status === 'fulfilled'){
         onfulfilled(this.value)
     }
    
     if (this.status === 'rejected'){
         onrejected(this.reason)
     }
   
 }

ok,我们的代码可以顺利执行了,但是Promise是用来解决异步问题的,而我们的代码是同步执行的,别着急,下面我们就完善最重要的异步逻辑

编写Promise的异步逻辑

异步初步实现

我们先测试一下如下代码


 let promise = new Promise((resolve,reject) =>{
    setTimeout(()=>{
         resolve('data')
     },2000)
 })

 promise.then(data =>{
     console.log(data)
 })

等待2s,发现并没有任何东西输出?^?而正常情况下会在两秒后输出data

原因是我们的then方法中的onfulfilled是同步执行的,它在执行时this.status依然为pending,并没有做到2s后在执行onfulfilled。

我们应该在合适的时间调用onfulfilled方法,这个时间就是开发者调用resolve时。所以我们先在状态为pending时把开发者传进来的onfulfilled方法存起来,再在resolve方法中执行即可。代码如下

function Promise(executor) {
  this.status = "pending";
  this.value = null;
  this.reason = null;
  //设置默认值为函数元
  this.onfulfilledFunc = Function.prototype;
  this.onrejectedFunc = Function.prototype;
  const resolve = (value) => {
    //判断value是否为Promise实例,如果是先对它执行then方法
    if (value instanceof Promise) {
      return value.then(resolve, reject);
    }

    if (this.status === "pending") {
      this.value = value;
      this.status = "fulfilled";
      this.onfulfilledFunc(this.value);
    }
  };
  const reject = (reason) => {
    if (this.status === "pending") {
      this.reason = reason;
      this.status = "rejected";
      this.onrejectedFunc(this.reason);
    }
  };
  executor(resolve, reject);
}
Promise.prototype.then = function (onfulfilled, onrejected) {
  if (this.status === "fulfilled") {
    onfulfilled(this.value);
  }

  if (this.status === "rejected") {
    onrejected(this.reason);
  }
  if (this.status === "pending") {
    this.onfulfilledFunc = onfulfilled;
    this.onrejectedFunc = onrejected;
  }
};

放入任务队列

我们看下面这个测试

let promise = new Promise((resolve, reject) => {
  resolve("data");
});
promise.then((data) => {
  console.log(data);
});
console.log(1);

我们代码输出了data,再输出了1。

而正常的话,先输出1,再输出data。

原因是console.log(1)是同步任务,只有当同步任务的栈为空时,才会执行异步的任务队列。因此需要将resolve与reject放到任务队列中。

我们就简单的放到setTimeout中,保证异步执行(其实Promise属于microtasks,这样做并不严谨,可以用MutationObserver来模拟),代码如下

 function Promise(executor) {

     this.status = 'pending'
     this.value = null
     this.reason = null
     this.onfulfilledFunc = Function.prototype
     this.onrejectedFunc = Function.prototype
     const resolve = value => {
        if(value instanceof Promise){
             return value.then(resolve,reject)
         }
         setTimeout(()=>{
               if (this.status === 'pending') {
                 this.value = value
                 this.status = 'fulfilled'
                 this.onfulfilledFunc(this.value)
             }   
         
         })
           

     }
     const reject = reason => {
         setTimeout(()=>{
             if (this.status === 'pending') {
                 this.reason = reason
                 this.status = 'rejected'
                 this.onrejectedFunc(this.reason)
             }
         })
            
    

     }
     executor(resolve, reject)
 }
 Promise.prototype.then = function (onfulfilled , onrejected ) {
     if (this.status === 'fulfilled'){
          onfulfilled(this.value)
     }
    
     if (this.status === 'rejected'){
         onrejected(this.reason)
     }
     if (this.status === 'pending'){
         this.onfulfilledFunc = onfulfilled
         this.onrejectedFunc = onrejected
     }
   
 }

储存回调函数

看下面测试

// 测试
 let promise = new Promise((resolve,reject) =>
 {
     setTimeout(()=>{
         resolve('data')
     },2000)
 })
 promise.then(data=>{
    console.log(`1:${data}`)
 })
 promise.then(data=>{
     console.log(`2:${data}`)
 })

理论上应该输出

1:data
2:data

但只输出了2:data原因是第二个then方法中的onFulfilledFunc会覆盖第一个then方法中的onFulfilledFunc

那我们需要将then方法的onFulfilledFunc储存到一个数组中,在Promise被决议时依次执行onFulfilldedArray数组内的方法即可。而onRejected同理,代码如下

 function Promise(executor) {

     this.status = 'pending'
     this.value = null
     this.reason = null
     this.onfulfilledArray = []
     this.onrejectedArray = []
     const resolve = value => {
        if(value instanceof Promise){
             return value.then(resolve,reject)
         }
         setTimeout(()=>{
               if (this.status === 'pending') {
                 this.value = value
                 this.status = 'fulfilled'
                   //forEach方法依次执行方法
                 this.onfulfilledArray.forEach(func=>{
                     func(value)
                 })
             }   
         
         })
           

     }
     const reject = reason => {
         setTimeout(()=>{
             if (this.status === 'pending') {
                 this.reason = reason
                 this.status = 'rejected'
                 this.onrejectedArray.forEach(func=>{
                     func(reason)
                 })
             }
         })
            
    

     }
     //如果在构造函数中出错,将会自动触发Promsie实例状态变为rejected
     try{
          executor(resolve, reject)
     }catch(e){
         reject(e)
     }
    
 }
 Promise.prototype.then = function (onfulfilled , onrejected ) {
     if (this.status === 'fulfilled'){
          onfulfilled(this.value)
     }
    
     if (this.status === 'rejected'){
         onrejected(this.reason)
     }
     if (this.status === 'pending'){
         //将onfulfilled储存到一个数组中
         this.onfulfilledArray.push(onfulfilled)
         this.onrejectedFunc.push(onrejected)
     }
   
 }

接下来继续实现then方法的链式调用效果

promsie then 的链式调用

链式调用的初步实现

首先看如果第一个then方法返回非promise实例的情况

  let promise = new Promise((resolve,reject) =>
  {
      setTimeout(()=>{
         resolve('data')
      },3000)
  })
  promise.then(data=>{
      console.log(`first ${data}`)
      return `second ${data} `
  })
 .then(data=>{
      console.log(data)
   })

每个then方法都应该返回一个promise实例

 Promise.prototype.then = function (onfulfilled , onrejected ) {
     //Promise穿透实现,如果给then()函数传递非函数类型的值时,则给一个默认值,该默认值是返回其参数的函数
    onfulfilled = typeof onfulfilled === 'function'?onfulfilled:data=>data
    onrejected = typeof onrejected === 'function' ? onrejected:error=>{throw error};
   //  promise2将作为then方法的返回值
    let promise2

  if (this.status === 'fulfilled'){
    return promise2 = new Promise((resolve,reject)=>{
        setTimeout(()=>{
            try{
               //这个新的promise2的经过resolve处理的值为 onfulfilled 的执行结果
               let x = onfulfilled(this.value)
               resolve(x)
            }catch(e){
                reject(e)
           }
        })
    })
 }
    
if (this.status === 'rejected'){
    return promise2 = new Promise((resolve,reject)=>{
        setTimeout(()=>{
            try {
                //这个新的promise2的经过resolve处理的值为 onrejected的执行结果
               let x = onrejected(this.value)
                 resolve(x)
             } catch (e) {
                 reject(e)
            }
         })
    })
 }
     //promise2 在异步处理结束后,依次执行onfulfilledArray或onRejectedArray数组的函数时,被决议
     //而onfulfilledArray或onRejectedArray数组的函数切换promise2的状态,并进行决议
    if (this.status === 'pending'){
       return promise2 =new Promise((resolve,reject)=>{
         this.onfulfilledArray.push(()=>{
            try{
                 let x = onfulfilled(this.value)
                 resolve(x)
            }catch(e){
                 reject(e)
             }
        })
         this.onrejectedArray.push(()=>{
             try{
                 let x = onrejected(this.reason)
                 resolve(x)
             }
             catch(e){
                 reject(e)
             }
         })
       })
     }
   
 }

代码执行成功

完善链式调用

我们看这个例子

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('data')
    }, 3000)
})
promise.then(data => {
    console.log(data)
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`${data} next`)
        }, 1000)
    })
})
.then(data => {
        console.log(data)
    })

根据promise/A+规范,x既可以是一个普通值,也可以是一个promise实例,所以我们抽象出resolvePromise方法进行统一处理,代码如下

Promise.prototype.then = function (onfulfilled , onrejected ) {
    onfulfilled = typeof onfulfilled === 'function'?onfulfilled:data=>data
    onrejected = typeof onrejected === 'function' ? onrejected:error=>{throw error};
    
    let promise2

     if (this.status === 'fulfilled'){
         return promise2 = new Promise((resolve,reject)=>{
             setTimeout(()=>{
                 try{
             
                     let x = onfulfilled(this.value)
                     resolvePromise(promise2,x,resolve,reject)
                 }catch(e){
                     reject(e)
                 }
             })
         })
     }
    
     if (this.status === 'rejected'){
         return promise2 = new Promise((resolve,reject)=>{
             setTimeout(()=>{
                 try {
                     let x = onrejected(this.value)
                     resolvePromise(promise2,x,resolve,reject)
                 } catch (e) {
                     reject(e)
                 }
             })
         })
     }
    if (this.status === 'pending'){
      return promise2 =new Promise((resolve,reject)=>{
        this.onfulfilledArray.push(()=>{
            try{
                let x = onfulfilled(this.value)
                resolvePromise(promise2,x,resolve,reject)
            }catch(e){
                reject(e)
            }
        })
        this.onrejectedArray.push(()=>{
            try{
                let x = onrejected(this.reason)
                resolvePromise(promise2,x,resolve,reject)
            }
            catch(e){
                reject(e)
            }
        })
      })
    }
   
}

最后实现一下resolvePromise函数

const resolvePromise = (promise2, x, resolve, reject) => {
    //当x和promise2相等时,也就是在onfulfilled返回promise2时,执行reject
    if (x === promise2) {
        reject(new TypeError('error due to circular reference'))
    }
    //是否已经执行过onfulfilled或onrejected
    let consumed = false

    let thenable
    //如果返回的是Promsie类型
    if (x instanceof Promise) {
        if (x.status === 'pending') {
            x.then(function (data) {
                //递归调用
                resolvePromise(promise2, data, resolve, reject)}, reject
            )
        } else {
            x.then(resolve, reject)
        }
        return
    }

    let isComplexResult = targert => (typeof targert === 'function' || typeof targert === 'object') && (targert !== null)
    //如果返回的是疑似Promsie类型
    if (isComplexResult(x)) {
        try {
            thenable = x.then
            //判断返回值是否为promise类型
            if (typeof thenable === 'function') {
                thenable.call(x, function (data) {
                    if (consumed) {
                        return
                    }
                    consumed = true
                    //递归调用
                    return resolvePromise(promise2, data, resolve, reject)
                }, function (error) {
                    if (consumed) {
                        return
                    }
                    consumed = true
                    return reject(error)
                })
            }
            else {
                resolve(x)
            }
        } catch (e) {
            if (consumed) {
                return
            }
            consumed = true
            return reject(e)
        }
    }
    //如果是普通值的话
    else {
        resolve(x)
    }

}

自此我们彻底搞定了then的链式调用,这也是手写promise最核心的部分,下一篇我们来一起实现catch方法以及其他的静态方法吧