手写Promise

117 阅读4分钟

首先分类讨论

定出大纲,promise的状态,异步函数的回调执行,链式调用。

Promise的状态

Promise在英文当中语义承诺,承诺是不允许被更改的,在Promise当中亦是如此。 总共有三种状态, fulfilled成功 rejected失败 pending执行中

一开始是pending然后才会变成fulfilled和rejected状态

异步执行

如何异步执行,我们使用setTimeOut(()=>{},0) 进行模拟异步

如何链式调用

进行.then(()=>{}).then(()=>{})这种链式调用

.then需要对then里面的内容进行判断是不是函数
如果是函数就拿到参数执行对于的函数
把这个值保存道全局的this.PromiseResult中
这样下一个.then就可以拿到上一个.then中处理完的结果进行处理
实现链式调用

伪代码

then(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
  if (this.PromiseState === 'fulfilled') {
      onFulfilled(this.PromiseResult)
  } else if (this.PromiseState === 'rejected') {
      onRejected(this.PromiseResult)
  }
}

开始实现代码

状态实现

const statusMap = {
  PENDING: "pending",
  FULFILLED: "fulfilled",
  REJECTED: "rejected",
 }

使用对象存储状态,便于统一管理和后期的拓展

实现promise类

1.jpg

我们先看整体,首先肯定有一个resolve方法和reject方法让我们去进行调用.
在constructor里面也是接受一个fn方法
里面将resolve和reject方法当作参数进行传入

constructor初始化操作

initValue方法是用来初始化值和状态
initBind是用来绑定this指向 在函数调用的时候绑定的this始终指向hdPromise实例

代码

   initBind() {
      this.resolve = this.resolve.bind(this)
      this.reject = this.reject.bind(this)
  }

  initValue() {
      this.PromiseResult = null 
      this.PromiseState = statusMap.PENDING 
  }

简单代码

这里就不过多解释了,大家有点基础的肯定都懂。

const statusMap = {
 PENDING: "pending",
 FULFILLED: "fulfilled",
 REJECTED: "rejected",
}

class hdPromise {
 constructor(fn) {
     this.initValue()
     this.initBind()
     fn(this.resolve, this.reject)
 }

 initBind() {
     this.resolve = this.resolve.bind(this)
     this.reject = this.reject.bind(this)
 }

 initValue() {
     this.PromiseResult = null 
     this.PromiseState = statusMap.PENDING 
 }

 resolve(value) {
     this.PromiseState = statusMap.FULFILLED
     this.PromiseResult = value
 }

 reject(reason) {
     this.PromiseState = statusMap.REJECTED
     this.PromiseResult = reason
 }
}

问题1

状态没有不可变

下面一段的是代码执行结果从fulfilled变成了rejected

const test = new hdPromise((resolve, reject) => {
 resolve('成功')
 reject('失败')
})


console.log(test)

执行结果

2.jpg

解决

加if进行判断即可

 resolve(value) {
   if (this.PromiseState !== statusMap.PENDING) return
   this.PromiseState = statusMap.FULFILLED
   this.PromiseResult = value
 }

 reject(reason) {
   if (this.PromiseState !== statusMap.PENDING) return
   this.PromiseState = statusMap.REJECTED
   this.PromiseResult = reason
 }

问题2

捕捉错误执行reject

Promise中有throw的话,会去执行reject。使用try catch进行错误捕获

代码

try {
        fn(this.resolve, this.reject)
      } catch (e) {
        this.reject(e)
    }  

执行.then

需要完成的任务

then接收两个回调,一个是成功回调,一个是失败回调 根据对应状态执行
如果resolve或reject在定时器里,则定时器结束后再执行then
then支持链式调用,下一次then执行受上一次then返回值的影响

支持定时器

如果then里面有setTimeOut定时器500ms后执行

如何保证then会在500ms后进行处理里面的回调函数

那怎么知道有没有执行定时器呢,只要状态是pending说明还没有进行执行

可以使用数组来进行保存对应的回调函数

实现then方法

 then(onFulfilled, onRejected) {
    onFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (val) => val
    onRejected = this.isFunction(onRejected)
      ? onRejected
      : (err) => {
        throw err
      }
    if (this.PromiseState === statusMap.FULFILLED) {
      onFulfilled(this.PromiseResult)
    } else if (this.PromiseState === statusMap.REJECTED) {
      onRejected(this.PromiseResult)
    } else if (this.PromiseState === statusMap.PENDING) {
      this.resolveStack.push(onFulfilled.bind(this))
      this.rejectStack.push(onRejected.bind(this))
    }
  }

其他方法

initValue() {
  this.PromiseResult = null
  this.PromiseState = statusMap.PENDING
  this.resolveStack = []
  this.rejectStack = []
}

resolve(value) {
  if (this.PromiseState !== statusMap.PENDING) return
  this.PromiseState = statusMap.FULFILLED
  this.PromiseResult = value
  while (this.resolveStack.length) {
    this.resolveStack.shift()(this.PromiseResult)
  }
}

reject(reason) {
  if (this.PromiseState !== statusMap.PENDING) return
  this.PromiseState = statusMap.REJECTED
  this.PromiseResult = reason
  while (this.rejectStack.length) {
    this.rejectStack.shift()(this.PromiseResult)
  }
}

链式调用

then支持链式调用,下一次then执行受上一次then返回值的影响

关键知识点

1、then方法本身会返回一个新的Promise对象
2、如果返回值是promise对象,返回值为成功,新promise就是成功
3、如果返回值是promise对象,返回值为失败,新promise就是失败
4、如果返回值非promise对象,新promise对象就是成功,值为此返回值

then(onFulfilled, onRejected) {
    onFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (val) => val
    onRejected = this.isFunction(onRejected) ? onRejected : (err) => { throw err }

    const thenResult = new HdPromise((resolve, reject) => {
      const resolvePromise = (cb) => {
        try {
          const res = cb(this.PromiseResult)
          if (res === thenResult) {
            throw new Error('请不要返回自身')
          }
          if (res instanceof HdPromise) {
            res.then(resolve, reject)
          } else {
            resolve(res)
          }
        } catch (err) {
          reject(err)
        }
      }

      if (this.PromiseState === statusMap.FULFILLED) {
        resolvePromise(onFulfilled)
      } else if (this.PromiseState === statusMap.REJECTED) {
        resolvePromise(onRejected)
      } else if (this.PromiseState === statusMap.PENDING) {
        this.resolveStack.push(resolvePromise.bind(this, onFulfilled))
        this.rejectStack.push(resolvePromise.bind(this, onRejected))
      }
    })
    return thenResult
  }

细节讲解

通过res instanceof HdPromise 进行判断是不是HdPromise实例
来决定可不可以执行res.then
使用resolvePromise 去处理回调函数

整体进行包装成HdPromise来进行返回 return出去

一些边界条件处理

res === thenResult
返回结果不能是自己 catch (err) 处理错误

微任务-异步执行

使用setTimeout进行模拟

3.jpg

常见手写体all-race

实际面试当中让你手写Promise是很少的
但是手写all和race是很多的
这里有一定的技巧

手写all

代码


var a = new Promise(resolve=>{
  setTimeout(()=>{
      console.log(1)
      resolve(1)
  },1000)
})

var b = new Promise(resolve=>{
  setTimeout(()=>{
      console.log(2)
      resolve(2)
  },2000)
})
function all(promises) {
  let len = promises.length, res = []
  console.log(len,promises,'promises')
  if (len) {
    return new Promise(function (resolve, reject) {
        for(let i=0; i < len; i++){
            let promise = promises[i];
            promise.then(response => {
                res[i] = response
                // 当返回结果为最后一个时
                if (res.length === len) {
                    resolve(res)
                }

            }, error => {
                reject(error)
            })

        }
    })
}
}
all([a,b]).then(res=>{console.log(res,'res')})

细节

就是把所有的promise执行结果都放入一个数组当中
如果数组长度等于所有的promise数组长度说明执行完毕
可以进行resolve(res)出去
通过res[i] = 执行结果 我们就可以保证结果的执行顺序
自身包裹成一个Promise进行返回

手写race

race就更简单直接resolve出去最快的哪一个就可以

代码

function race(promises) {
 let len = promises.length, res = []
 if (len) {
   return new Promise(function (resolve, reject) {
       for(let i=0; i < len; i++){
           let promise = promises[i];
           promise.then(res => {
               resolve(res)

           }, error => {
               reject(error)
           })

       }
   })
}