万字!适合新手入门向的详细手写Promise解析

关于Promise一直是前端面试的重难点,对于Promise很多人其实一直是一知半解,毕竟一直用的是axios以及Promise附带的async await而对于最原始的then却接触的比较少,而在面对复杂情景下代码执行顺序时却一头雾水,其实Promise要完全掌握最好的方式就是看会源码会写源码。

接下来我们对Promise有简到繁进行解析与自定义
在面试时我们有时候会被要求进行手写Promise,接到这个题时可能不知道从哪里开始写起。很多人第一时间都想一口气写出来,其实这是陷入了一个误区,在没有需求提示的情况下一口气写代码无异于直接背代码,而Promise几百行的源码不是一下就能全部想起来的,自然一时间就无从下手。
最好的方式就是需求带动代码接下来我们看下嘛几段代码:
代码1:

let promise = new TPromise((resolve, reject) => {
          resolve('成功')
   	})

从上面我们可以看到很明显Promise是一个类,而constructor传入的是一个执行函数,执行函数在constructor执行时又传入了两个函数,面对前一句话新手可能会一头雾水不知道代码改怎么写了,函数该在哪定义,又应该在哪执行成了Promise源码的第一个难题。其实只要记住接下来这句话就很好理解了,“函数在哪里传入就在哪里定义”。很明显在执行函数传入,那么就在执行函数的执行环境进行定义,我们就能得到如下代码:

class TPromise{
  constructor(executor){
    this.resolve = (value)=>{
      
    }
    this.reject = (reason)=>{

    }
    executor(this.resolve,this.reject)
  }
}

不知不觉中其实就已经掌握了高阶函数的精髓。
接下来再对代码进行分析,我们都知道Promise内置三种状态:pending,resolved,rejected。pending可以转换到resolved,rejected但不可逆,且resolved,rejected不可互相转换。
前半句很好理解,只要源码中不要出现resolved->pending这种逻辑的代码就行,而后半句其实意思就是如果已经转换为resolved就拒绝执行reject函数(rejected也是一样)。
那么我们就可以对代码进行优化实现:

class TPromise{
  PromiseState = 'pending'
  constructor(executor){
    this.resolve = (value)=>{
      if(this.PromiseState === 'pending'){
        this.PromiseState = 'resolved'
      }
    }
    this.reject = (reason)=>{
      if(this.PromiseState === 'pending'){
        this.PromiseState = 'rejected'
      }
    }
    executor(this.resolve,this.reject)
  }
}

我们有注意到需求代码中有传入一个值:“成功”,那么就先将这个值保存下来:

class TPromise{
  PromiseState = 'pending'
  PromiseResult = null
  constructor(executor){
    this.resolve = (value)=>{
      if(this.PromiseState === 'pending'){
        this.PromiseState = 'resolved'
        this.PromiseResult = value
      }
    }
    this.reject = (reason)=>{
      if(this.PromiseState === 'pending'){
        this.PromiseState = 'rejected'
        this.PromiseResult = reason
      }
    }
    executor(this.resolve,this.reject)
  }
}

到这里其实constructor主体逻辑已经完成的一半,接下来我们看最重要的then函数
还是老样子,先看怎么使用then函数:

let p = new Promise((resolve, reject) => {
  resolve(1)
})

p.then((value) => {
  console.log(value)
})

我们可以看到then函数定义并传入一个执行函数,这个函数接收一个值,这时候我们就可以把上面保存的值传入进来:

class TPromise {
  PromiseState = 'pending'
  PromiseResult = null
  constructor(executor) {
    this.resolve = (value) => {
      if (this.PromiseState === 'pending') {
        this.PromiseState = 'resolved'
        this.PromiseResult = value
      }
    }
    this.reject = (reason) => {
      if (this.PromiseState === 'pending') {
        this.PromiseState = 'rejected'
        this.PromiseResult = reason
      }
    }
    executor(this.resolve, this.reject)
  }
  then(resolvedFn, rejectedFn) {
    if (this.PromiseState === 'resolved') {
      resolvedFn(this.PromiseResult)
    }
    if (this.PromiseState === 'rejected') {
      rejectedFn(this.PromiseResult)
    }
  }
}

接下来进行第二步捕获异常:我们知道在Promise中抛错会使rejectedFn执行那么利用try catch捕获异常后传给reject后改变PromiseState就会使rejectedFn执行并拿到错误值

class TPromise {
  PromiseState = 'pending'
  PromiseResult = null
  constructor(executor) {
    this.resolve = (value) => {
      if (this.PromiseState === 'pending') {
        this.PromiseState = 'resolved'
        this.PromiseResult = value
      }
    }
    this.reject = (reason) => {
      if (this.PromiseState === 'pending') {
        this.PromiseState = 'rejected'
        this.PromiseResult = reason
      }
    }
    try {
      executor(this.resolve, this.reject)
    }catch (error) {
      this.reject(error)
    }
  }
  then(resolvedFn, rejectedFn) {
    if (this.PromiseState === 'resolved') {
      resolvedFn(this.PromiseResult)
    }
    if (this.PromiseState === 'rejected') {
      rejectedFn(this.PromiseResult)
    }
  }

now,我们已经做到将then函数实现高阶函数调用,但是Promise其实是支持异步的,这也就意味着then函数传入的两个执行函数,需要在执行resolve后再执行,这里可以使用消息订阅模式又叫生产者消费者模式,简而言之就是将then传入的执行函数暂时保存在实例的属性上(注意保存的是柯里化后的函数,方便后续调用,用过react的应该知道),接下来再在resolve内部写入执行刚刚保存函数的代码

class TPromise {
  PromiseState = 'pending'
  PromiseResult = null
  resolve_then_callbacks = []
  reject_then_callbacks = []
  constructor(executor) {
    this.resolve = (value) => {
      if (this.PromiseState === 'pending') {
        this.PromiseState = 'resolved'
        this.PromiseResult = value
        this.resolve_then_callbacks.forEach((callback) => {
          callback()
        })
      }
    }
    this.reject = (reason) => {
      if (this.PromiseState === 'pending') {
        this.PromiseState = 'rejected'
        this.PromiseResult = reason
        this.reject_then_callbacks.forEach((callback) => {
          callback()
        })
      }
    }
    try {
      executor(this.resolve, this.reject)
    }catch (error) {
      this.reject(error)
    }
  }
  then(resolvedFn, rejectedFn) {
    if (this.PromiseState === 'resolved') {
      resolvedFn(this.PromiseResult)
    }
    if (this.PromiseState === 'rejected') {
      rejectedFn(this.PromiseResult)
    }
    if (this.PromiseState === 'pending') {
      this.resolve_then_callbacks.push(() => {//注意这里不单单传入的是函数变量,而是函数柯里化
        resolvedFn(this.PromiseResult)
      })
      this.reject_then_callbacks.push(() => {//之所以使用数组保存是因为then函数支持多次调用有效
        rejectedFn(this.PromiseResult)
      })
    }
  }
}

到这里我们已经完成了then函数的单次调用基本功能,我们看接下来的Promise需求代码:

let p = new Promise((resolve, reject) => {
  resolve(1)
})

p.then((value) => {
  console.log(value)
  return ‘ok’
}).then((value) => {
  console.log(value)
})

我们可以注意到then函数后面又跟了一个.then,这意味着then函数有返回值而且这个返回值甚至还能调用then函数,那么这个返回值是什么就很清楚了,我们只需要再返回一个Promsie的实例就行了,但返回一个实例还不行,因为我们知道then函数能够执行是因为类Promise的执行器函数内部执行了resolve函数改变了PromiseState,那么意味着我们初始化并返回实例时,需要在初始化时执行resolve或reject函数。
那么问题来了resolve或reject函数是需要传入值的,那我们传入什么值呢,我们依旧从需求入手,发现“正版”Promsie是可以实现值传递的,那我们就传入上一个then函数执行后返回的值

then(resolvedFn, rejectedFn) {
    return new IPromise((resolve, reject) => {
      if (this.PromiseState === 'resolved') {
        let result = resolvedFn(this.PromiseResult)
        resolve(result)
      }
      if (this.PromiseState === 'rejected') {
        let result = rejectedFn(this.PromiseResult)
        resolve(result)
      }
      if (this.PromiseState === 'pending') {
        this.resolve_then_callbacks.push(() => {
          let result = resolvedFn(this.PromiseResult)
          resolve(result)
        })
        this.reject_then_callbacks.push(() => {
          let result = rejectedFn(this.PromiseResult)
          resolve(result)
        })
      }
    })
  }

注:完整代码越来越大,方便起见,其余代码如若未有改动这里就不贴了,最后我会贴上完整代码!

上面的代码只是做到正常返回值的情况,而我们“真正”Promise是可以返回Promsie实例的,大家不要小看这个功能,如果可以返回Promise实例也就意味着在可以在执行器函数再继续进行异步操作,这在很多异步任务是非常方便的,如果你做过经典的面试题红绿灯就会知道这一点有多么重要。

我们回来继续分析,既然可以返回Promise实例,那么这个实例就能执行then函数,那么我们就能拿到传入的值传到下一个then中嘛。

  then(resolvedFn, rejectedFn) {
    return new IPromise((resolve, reject) => {
      if (this.PromiseState === 'resolved') {
        try {
          let result = resolvedFn(this.PromiseResult)
          if (result instanceof IPromise) {
            result.then(
              (v) => {
                resolve(v)
              },
              (r) => {
                reject(r)
              }
            )
          } else {
            resolve(result)
          }
        } catch (error) {
          reject(error)
        }
      }
      if (this.PromiseState === 'rejected') {
        let result = rejectedFn(this.PromiseResult)
        resolve(result)
      }
      if (this.PromiseState === 'pending') {
        this.resolve_then_callbacks.push(() => {
          let result = resolvedFn(this.PromiseResult)
          resolve(result)
        })
        this.reject_then_callbacks.push(() => {
          let result = rejectedFn(this.PromiseResult)
          resolve(result)
        })
      }
    })
  }

其他返回函数也是一样,这里碍于篇幅我就不一一写了。最后其实可以把函数提出来,后面的代码会有所体现。

then函数已经完成了九成,还有就是一些“容错”代码;
我们有时候会发现,并不是所有then函数都会传入函数返回点什么东西,但是代码依旧能按我们预想的执行顺序执行(比如不返回执行resolve而不是reject)。既然他不返回我们帮他返回就是了,只是值返回undefined就是了

then(resolvedFn, rejectedFn) {
		if (typeof rejectedFn !== 'function') {
      //异常穿透
      rejectedFn = (reason) => {
        throw reason
      }
    }
    if (typeof resolvedFn !== 'function') {
      //值传递
      resolvedFn = (value) => {
        return value
      }
    }
    return new IPromise((resolve, reject) => {
      const tryReturn = (fn) => {
        try {
          let result = fn(this.PromiseResult)
          if (result instanceof IPromise) {
            result.then(
              (v) => {
                resolve(v)
              },
              (r) => {
                reject(r)
              }
            )
          } else {
            resolve(result)
          }
        } catch (error) {
          reject(error)
        }
      }
      if (this.PromiseState === 'resolved') {
        tryReturn(resolvedFn)
      }
      if (this.PromiseState === 'rejected') {
        tryReturn(rejectedFn)
      }
      if (this.PromiseState === 'pending') {
        this.resolve_then_callbacks.push(() => {
          tryReturn(resolvedFn)
        })
        this.reject_then_callbacks.push(() => {
          tryReturn(rejectedFn)
        })
      }
    })
  }

到这里就已经完成Promise的基础功能代码了
我们看一下劳动成果:

class TPromise {
  PromiseState = 'pending'
  PromiseResult = null
  resolve_then_callbacks = []
  reject_then_callbacks = []
  constructor(executor) {
    this.resolve = (value) => {
      if (this.PromiseState === 'pending') {
        this.PromiseState = 'resolved'
        this.PromiseResult = value
        this.resolve_then_callbacks.forEach((callback) => {
          callback()
        })
      }
    }
    this.reject = (reason) => {
      if (this.PromiseState === 'pending') {
        this.PromiseState = 'rejected'
        this.PromiseResult = reason
        this.reject_then_callbacks.forEach((callback) => {
          callback()
        })
      }
    }
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }
  then(resolvedFn, rejectedFn) {
    return new IPromise((resolve, reject) => {
      const tryReturn = (fn) => {
        try {
          let result = fn(this.PromiseResult)
          if (result instanceof IPromise) {
            result.then(
              (v) => {
                resolve(v)
              },
              (r) => {
                reject(r)
              }
            )
          } else {
            resolve(result)
          }
        } catch (error) {
          reject(error)
        }
      }
      if (this.PromiseState === 'resolved') {
        tryReturn(resolvedFn)
      }
      if (this.PromiseState === 'rejected') {
        tryReturn(rejectedFn)
      }
      if (this.PromiseState === 'pending') {
        this.resolve_then_callbacks.push(() => {
          tryReturn(resolvedFn)
        })
        this.reject_then_callbacks.push(() => {
          tryReturn(rejectedFn)
        })
      }
    })
  }
}

这里大家可能会发现我的代码只要是函数就都是箭头函数的形式,这是因为我所有的代码都是在类中写的并且我希望代码中的所有this都是指向类实例的,就运用闭包以及箭头函数的特性,使代码不用纠结于this的指向。
还有一个就是catch捕获异常的函数,因为前面已经顺手写了异常穿透的代码,所以现在写起来就很简单

  catch(rejectFn) {
    return this.then(undefined, rejectFn)
  }

最后就是不管是 resolve 还是 reject 都会调用 finally 。那么相当于 fianlly 方法替使用者分别调用了一次 then 的 resolved 和 rejected 状态回调。

finally (fn) {
  return this.then(
    (value) => {
      fn();
      return value;
    },
    (reason) => {
      fn();
      throw reason;
    }
  );
};

接下来就要轻松许多了,前人栽树后人乘凉。让我们利用现有代码来完成几个类上面的静态方法:

  1. resolve,返回一个Promise实例,同时也支持返回值是Promise
 static resolve(data) {
    return new IPromise((resolve, reject) => {
      if (data instanceof IPromise) {
        data.then(
          (v) => {
            resolve(v)
          },
          (r) => {
            reject(r)
          }
        )
      } else {
        resolve(data)
      }
    })
  }
  1. reject,与resolve不同的是,它不管返回什么都是失败Promise
static reject(data) {
    return new IPromise((resolve, reject) => {
      reject(data)
    })
  }
  1. all,接受Promise数组,按顺序返回Promise结果。注意是按顺序,所以收获结果时不能直接push
static all(data) {
    return new IPromise((resolve, reject) => {
      let count = 0
      let resArr = []
      // 遍历
      for (let i = 0; i < data.length; i++) {
        data[i].then(
          (v) => {
            count++
            resArr[i] = v //注意顺序
            if (count === data.length) {
              resolve(resArr)
            }
          },
          (r) => {
            reject(r)
          }
        )
      }
    })
  }
  1. race,接受Promise数组,返回最先响应的结果
static race(data) {
    return new IPromise((resolve, reject) => {
      for (let i = 0; i < data.length; i++) {
        data[i].then(
          (v) => {
            resolve(v)
          },
          (r) => {
            reject(r)
          }
        )
      }
    })
  }
  1. allSettled,方法只有等到参数数组的所有 Promise 实例都发生状态变更,返回的 Promise 实例才会发生状态变更,无论是执行 resolve 回调还是 reject 回调的状态。
static allSettled(promises){
	return new IPromise((resolve, reject) => {
    if (promises.length === 0) {
      resolve([]);
    } else {
      let result = [];
      let index = 0;
      for (let i = 0; i < promises.length; i++) {
        promises[i]
          .then(
            (value) => {
              result[i] = {
                status: 'fulfilled',
                value,
              };
            },
            (reason) => {
              result[i] = {
                status: 'rejected',
                reason,
              };
            }
          )
          .finally(() => {
            if (++index === promises.length) {
              return resolve(result);
            }
          });
      }
    }
  });
}
  1. any,返回任意一个最快执行 resolve 回调的 Promise 实例
static any(promises){
		return new IPromise((resolve, reject) => {
    if (promises.length === 0) {
      return resolve();
    } else {
      let result = [];
      let index = 0;
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(
          (value) => {
            return resolve(value);//有结果就返回
          },
          (reason) => {
            result[i] = reason;
            if (++index === promises.length) {//不断计数,直到都是失败的Promise
              return reject(new AggregateError(result));
            }
          }
        );
      }
    }
  });
}

到这里,所有Promise代码其实都已经完成了,但其实一直有一个坑没有填,也是Promsie的灵魂,就是在Promsie中then执行的的函数其实并不是在这一次事件循环中执行,而是在下一次事件循环中的宏任务中执行,所以我们的代码要做一些许修改;只需要在callbacks遍历执行函数这一段的代码放入异步队列,还有一个就是tryreturn代码片段放入异步队列即可

你可能感兴趣的:(js小技巧,javascript,开源)