手写Promise核心全过程

87 阅读6分钟

手写Promise核心全过程

最近开始学习一些核心原理,以便于更好地对于JavaScript的一些特性进行理解,第一站就是手写Promise,学习完成之后楼主重新对于书写promise的整个过程进行了梳理,当中或许会有些错误,欢迎批评指正o( ̄▽ ̄)ブ

1. 搭建Promise骨架

// 三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class myPromise {

  constructor(func) {
    // myPromise实例创建的时候会先执行一遍传入的方法,所以直接执行func()
    // 并且需要将resolve函数和reject函数放入,给用户进行调用
    // 由于this问题,可选方案有两种
    // 一种是使用bind函数,另一种是直接定义这两个函数为箭头函数
    func(this.resolve.bind(this), this.reject.bind(this))
  }

  resolve() {}
  
  reject() {}
}

2. 添加状态转换等字段

  • 在Promise中,状态只能由pendingfulfilledrejected转换,并且一旦转换就不能逆转
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class myPromise {
  // 添加status字段,用来存储当前这个Promise的状态,默认状态为PENDING
  status = PENDING

  // 成功的结果
  result = undefined
  // 失败的原因
  reason = undefined

  constructor(func) {
    func(this.resolve.bind(this), this.reject.bind(this))
  }

  resolve(result) {
    // 判断当前状态,如果是待办,那么就将状态更改为成功
    if(this.status === PENDING) {
      this.status = FULFILLED
      // 修改状态为成功之后,需要将成功的结果传入当前这个promise的result字段
      this.result = result
    }
  }

  reject(reason) {
    // 同上,将状态更改为失败
    if(this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
    }
  }
}

3. 加入then方法

  • promise中的then方法会对于完成和失败两种状态进行不同的处理,执行传入的方法
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class myPromise {
  status = PENDING
  result = undefined
  reason = undefined
  constructor(func) {
    func(this.resolve.bind(this), this.reject.bind(this))
  }

  resolve(result) {
    if(this.status === PENDING) {
      this.status = FULFILLED
      this.result = result
    }
  }

  reject(reason) {
    if(this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
    }
  }

  /**
   * @param {Function} onFULFILLED 传入的成功回调
   * @param {Function} onREJECTED 传入的失败回调
   */
  then(onFULFILLED, onREJECTED) {
    // 调用then方法的时候,需要对于当前的状态进行判断,然后执行传入的对应方法
    if(this.status === FULFILLED) {
      // 成功的时候执行成功的回调
      onFULFILLED(this.result)
    } else if(this.status === REJECTED) {
      // 状态为失败那么就执行失败的回调函数
      onREJECTED(this.reason)
    }
  }
}

4. 处理异步以及多次调用

  • 当前已经完成的代码中,如果在外部resolve或reject函数是在异步请求中的,会导致状态没有变更,只有完成异步之后状态才会改变
const p = new myPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功')
  }, 1000)
})

p.then((res) => {
  console.log(res) // 没有执行到这个函数
})

  • 处理方案:对于没有执行的方法,先存储起来,然后等待状态改变之后就可以进行执行
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class myPromise {
  status = PENDING
  result = undefined
  reason = undefined

  // 增加两个回调函数执行栈
  fulfilledCallbacks = []
  rejectedCallbacks = []

  constructor(func) {
    func(this.resolve.bind(this), this.reject.bind(this))
  }

  resolve(result) {
    if(this.status === PENDING) {
      this.status = FULFILLED
      this.result = result
      // 状态改变之后从执行栈中弹出方法并且执行
      while(this.fulfilledCallbacks.length) {
        this.fulfilledCallbacks.shift()(this.result)
      }
    }
  }

  reject(reason) {
    if(this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
      // 与resolve相同的操作
      while(this.rejectedCallbacks.length) {
        this.rejectedCallbacks.shift()(this.result)
      }
    }
  }

  then(onFULFILLED, onREJECTED) {
    if(this.status === FULFILLED) {
      onFULFILLED(this.result)
    } else if(this.status === REJECTED) {
      onREJECTED(this.reason)
    } else {
      // 第三个状态:pending
      // 向执行栈中压入函数
      this.fulfilledCallbacks.push(onFULFILLED)
      this.rejectedCallbacks.push(onREJECTED)
    }
  }
}

5. 处理链式调用以及返回类型处理

  • promise原本的功能支持链式调用,并且原本的promise支持返回数据或者promise
  • 处理这一个功能只需要对于返回的数值进行接收然后进行处理即可
  • 链式调用则需要返回一个promise
  • 由于promise的构造函数是马上执行的,所以then的函数体放在新创建出来的promise里面即可
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class myPromise {
  status = PENDING
  result = undefined
  reason = undefined

  fulfilledCallbacks = []
  rejectedCallbacks = []
  
  constructor(func) {
    func(this.resolve.bind(this), this.reject.bind(this))
  }

  resolve(result) {
    if(this.status === PENDING) {
      this.status = FULFILLED
      this.result = result
      while(this.fulfilledCallbacks.length) {
        this.fulfilledCallbacks.shift()(this.result)
      }
    }
  }
  

  reject(reason) {
    if(this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
      while(this.rejectedCallbacks.length) {
        this.rejectedCallbacks.shift()(this.result)
      }
    }
  }
  
  // 处理返回类型函数
  resolvePromise(thenRes, resolve, reject) {
    if(thenRes instanceof myPromise) {
      // return true -> thenRes type is myPromise
      // 当thenRes是promise,那么就执行thenRes的then方法,并且将resolve和reject方法传进去
      thenRes.then(resolve, reject)
    } else {
      resolve(thenRes)
    }
  }

  then(onFULFILLED, onREJECTED) {
    let promise = new myPromise((resolve, reject) => {
      // 构造函数中的传入函数func()会立即执行
      if(this.status === FULFILLED) {
        const thenRes = onFULFILLED(this.result)
        // resolve(thenRes)
        this.resolvePromise(thenRes, resolve, reject)
      } else if(this.status === REJECTED) {
        const thenRes = onREJECTED(this.reason)
        this.resolvePromise(thenRes, resolve, reject)
      } else {
        this.fulfilledCallbacks.push(onFULFILLED)
        this.rejectedCallbacks.push(onREJECTED)
      }
    })
  
    // 返回myPromise,能够进行链式调用
    return promise
  }
}

6. 处理错误以及加上异步阻塞

  1. 在原版的promise中,如果在正常的进程里面抛出错误,那么会将错误打印出来
  2. 在原版的promise中,如果有长时间的定时器,那么会阻塞,等待定时器完成再进行后续的操作,并且then方法中的操作应该是异步的
  3. 在原版的promise中,返回promise自身是会报错的
const p = new Promise((resolve, reject) => {
  resolve('成功')
})
  
const p1 = p.then((res) => {
  console.log(res) // 成功
  return p1
})

p1.then((result) => {
  console.log(result)
}, (reason) => {
  console.log(reason.message) // Chaining cycle detected for promise #<Promise>
})

  • 为了达成这个效果,需要在resolvePromise这个方法中对于返回的thenRes和返回的promise自身进行判断
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class myPromise {
  status = PENDING
  result = undefined
  reason = undefined
  
  fulfilledCallbacks = []
  rejectedCallbacks = []
  constructor(func) {
    // 处理抛出错误
    try {
      func(this.resolve.bind(this), this.reject.bind(this))
    } catch(error) {
      this.reject(error)
    }
  }

  resolve(result) {
    if(this.status === PENDING) {
      this.status = FULFILLED
      this.result = result
      while(this.fulfilledCallbacks.length) {
        this.fulfilledCallbacks.shift()(this.result)
      }
    }
  }
  
  reject(reason) {
    if(this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
      while(this.rejectedCallbacks.length) {
        this.rejectedCallbacks.shift()(this.result)
      }
    }
  }
  
  resolvePromise(promise, thenRes, resolve, reject) {
    // 判断自身与thenRes是否一致
    if(promise === thenRes) {
      return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }

    if(thenRes instanceof myPromise) {
      thenRes.then(resolve, reject)
    } else {
      resolve(thenRes)
    }
  }
  
  then(onFULFILLED, onREJECTED) {
    let promise = new myPromise((resolve, reject) => {
      if(this.status === FULFILLED) {
        setTimeout(() => {
          try {
            const thenRes = onFULFILLED(this.result)
            // this.resolvePromise(thenRes, resolve, reject)
            this.resolvePromise(promise, thenRes, resolve, reject)
          } catch(error) {
            reject(error)
          }
        })
      } else if(this.status === REJECTED) {
        setTimeout(() => {
          try {
            const thenRes = onREJECTED(this.reason)
            this.resolvePromise(thenRes, resolve, reject)
          } catch(error) {
            reject(error)
          }
        })
      } else {
        // 添加异步阻塞,将函数处理成异步函数,再压入栈中
        this.fulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const thenRes = onFULFILLED(this.result)
              // this.resolvePromise(thenRes, resolve, reject)
              this.resolvePromise(promise, thenRes, resolve, reject)
            } catch(error) {
              reject(error)
            }
          })
        })
        this.rejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const thenRes = onREJECTED(this.reason)
              this.resolvePromise(thenRes, resolve, reject)
            } catch(error) {
              reject(error)
            }
          })
        })
      }
    })
  
    return promise
  }
}
  • 到此完成了基本的promise编写,后续需要完成promise的另外的实例方法

7. promise.then的空值传递

  • 在原本的promise中,如果then方法中没有指定传入的方法,那么就需要将resolve或者reject的结果向后传递
const p = new Promise((resolve, reject) => {
  resolve('成功')
})  

p.then()
 .then()
 .then()
 .then((res) => {
  console.log(res) // 成功
 })
  • 处理的方法是,只需要在then方法前对于onFULFILLEDonREJECTED方法进行判断并且传递即可
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class myPromise {
  status = PENDING
  result = undefined
  reason = undefined
  
  fulfilledCallbacks = []
  rejectedCallbacks = []
  
  constructor(func) {
    try {
      func(this.resolve.bind(this), this.reject.bind(this))
    } catch(error) {
      this.reject(error)
    }
  }
  
  resolve(result) {
    if(this.status === PENDING) {
      this.status = FULFILLED
      this.result = result
      while(this.fulfilledCallbacks.length) {
        this.fulfilledCallbacks.shift()(this.result)
      }
    }
  }
  
  reject(reason) {
    if(this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
      while(this.rejectedCallbacks.length) {
        this.rejectedCallbacks.shift()(this.result)
      }
    }
  }
  
  resolvePromise(promise, thenRes, resolve, reject) {
    if(promise === thenRes) {
      return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
  
    if(thenRes instanceof myPromise) {
      thenRes.then(resolve, reject)
    } else {
      resolve(thenRes)
    }
  }
 
  then(onFULFILLED, onREJECTED) {
    // 使用判断语句对于传入的两个参数进行判断
    // 如果是空值,那么就往下传递,不是空值,那么就返回给自身
    onFULFILLED = onFULFILLED ? onFULFILLED : (result) => result
    onREJECTED = onREJECTED ? onREJECTED : (reason) => { throw reason}

    let promise = new myPromise((resolve, reject) => {
      if(this.status === FULFILLED) {
        setTimeout(() => {
          try {
            const thenRes = onFULFILLED(this.result)
            this.resolvePromise(promise, thenRes, resolve, reject)
          } catch(error) {
            reject(error)
          }
        })
      } else if(this.status === REJECTED) {
        setTimeout(() => {
          try {
            const thenRes = onREJECTED(this.reason)
            this.resolvePromise(thenRes, resolve, reject)
          } catch(error) {
            reject(error)
          }
        })
      } else {
        this.fulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const thenRes = onFULFILLED(this.result)
              this.resolvePromise(promise, thenRes, resolve, reject)
            } catch(error) {
              reject(error)
            }
          })
        })
        this.rejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const thenRes = onREJECTED(this.reason)
              this.resolvePromise(thenRes, resolve, reject)
            } catch(error) {
              reject(error)
            }
          })
        })
      }
    })

    return promise
  }
}

未完待续。。。