JavaScript - 手写一个 Promise 方法

187 阅读4分钟

ES6新增了一个用于处理异步操作数据的一个原生API,能够解决回调地狱的产生

常用的方法

  • Promise.resolve()
  • Promise.reject()
  • Promise.then()
  • Promise.catch()
  • Promise.finally()
  • Promise.all()
  • Promise.race()
  • Promise.allSettled()
  • Promise.any()

接下来我们来使用代码来实现这些操作

实现Promise

首先我们在创建 Promise 实例,会传入两个函数用来表示成功的回调以及失败的回调,然后我们可以设计 Promise 类中的内容大概是这个样子的

  const PANDING = 'panding'
  const FULFILLED = 'fulfilled'
  const REJECTED = 'rejected'
  class Promise {
    constructor(fn) {
      this.state = PANDING
      this.resolveVal = null
      this.rejectVal = null
      fn(this.resolve.bind(this), this.reject.bind(this))
    }
    resolve(value) {
        if(this.state !== PANDING) {
          return
        }
        this.state = FULFILLED
        this.resolveVal = value || this.resolveVal
    }
    reject(value) {
        if(this.state !== PANDING) {
          return
        }
        this.state = REJECTED
        this.rejectVal = value || this.rejectVal
    }
  }

这样就实现了一个简单的Promise方法,并且更改状态之后就不可以在修改状态了,并且用两个变量来接收resolvereject两个方法传入的值

  • Promise.then

Promise.prototype.then = function(resolve, reject) {
  let s = setInterval(() => {
    if(this.state !== PANDING) {
      s && clearInterval(s)
      try {
        let success = null
        let fail = null
        // 判断 传入参数的类型并绑定this
        typeof resolve === 'function' && (success = getReturn(resolve.call(this, this.resolveVal)))
        typeof reject === 'function' && (fail = getReturn(reject.call(this, this.rejectVal)))
        if(success) return Promise.resolve(success)
        if(fail) return Promise.resolve(fail)
        return this
      } catch (error) {
        return Promise.reject(error)
      }
    }
  },4)
}

调用 then 方法时判断一下传入的是否是函数,如果是函数就直接调用,并把成功的参数传入 最后的 return this 则是为了实现链式结构

  • Promise.catch
  Promise.prototype.catch = function(reject) {
    let s = setInterval(() => {
      if(this.state !== PANDING) {
        s && clearInterval(s)
        try {
          try {
            let fail = null
            typeof reject === 'function' && (fail = getReturn(reject.call(this, this.rejectVal)))
            if(fail) return Promise.resolve(fail)
            return this
          } catch (error) {
            return Promise.reject(error)
          }
        } catch (error) {
          return Promise.reject(error)
        }
      }
    },4)
  }
  • Promise.finally
  Promise.prototype.finally = function(fn) {
    fn()
    return this
  }
  • Promise.resolve
  Promise.resolve = function(value) {
    if(value instanceof Promise) return value
    return new Promise((resolve) => {resolve(value)})
  }

这个稍微有些麻烦,我们需要判断传入的 value 是不是 Promise 类型的,如果是就可以直接返回,然后调用 .then 等方法,否则就需要我们创建一个 Promise 实例并返回

  • Promise.reject
  Promise.reject = function(value) {
    if(value instanceof Promise) return value
    return new Promise((resolve, reject) => {reject(value)})
  }

但是这里还有许多需要注意的地方,例如Promise.then中如果有 return 的话则需要以返回的状态和值为主,如果没有,就以上一次的状态和值为主 同时还需要对代码使用 try...catch... 进行容错处理,使得在 catch 中能够拿到错误信息

代码修改如下

  Promise.prototype.then = function(resolve, reject) {
    try {
      let success = null
      let fail = null
      // 判断 传入参数的类型并绑定this
      typeof resolve === 'function' && (success = getReturn(resolve.call(this, this.resolveVal)))
      typeof reject === 'function' && (fail = getReturn(reject.call(this, this.rejectVal)))
      if(success) return Promise.resolve(success)
      if(fail) return Promise.resolve(fail)
      return this
    } catch (error) {
      return Promise.reject(error)
    }
  }

  function getReturn(val) {
    if(typeof val === 'function') return val()
    return val
  }

实现Promise高级语法

  • Promise.all
  Promise.all = function(arr) {
    try {
      if(arr instanceof Array) {
        return new Promise((resolve, reject) => {
          let resolveVal = []
          let isReject = false
          for(let i = 0; i < arr.length; i++) {
            let res = getReturn(arr[i])
            if(res instanceof Promise) {
              res.then(res1 => {
                // 保证数组顺序返回正确
                resolveVal[i] = res1
              }).catch(err => {
                reject(err)
                isReject = true
              })
            }
            else resolveVal.push(res)
            if(isReject) break
          } 
          resolve(resolveVal)
        })
      } else throw new Error('Promise.all must receive Array')
    } catch (error) {
      if(error instanceof Error) throw error
      return Promise.reject(error)
    }
  }
  • Promise.race
  Promise.race = function(arr) {
    try {
      if(arr instanceof Array) {
        return new Promise((resolve, reject) => {
          if(arr.length === 0) resolve()
          for(let i = 0; i < arr.length; i++) {
            let res = getReturn(arr[i])
            if(res instanceof Promise) {
              res.then(res1 => {
                if(res1) {
                  resolve(res)
                }
              }).catch(err => {
                if(err) {
                  reject(err)
                }
              })
            } else {
              resolve(res)
              break;
            }
          }
        })
      } else throw new Error('Promise.all must receive Array')
    } catch (error) {
      if(error instanceof Error) throw error
      return Promise.reject(error)
    }
  }
  • promise.allSettled
  Promise.allSettled = function(arr) {
    try {
      if(arr instanceof Array) {
        return new Promise((resolve, reject) => {
          let resolveVal = []
          let length = 0
          if(arr.length === 0) resolve([])
          for(let i = 0; i < arr.length; i++) {
            let res = getReturn(arr[i])
            if(res instanceof Promise) {
              res.then(res1 => {
                if(res1) {
                  resolveVal[i] = {
                    status: FULFILLED,
                    value: res1
                  }
                  if(++length === arr.length) resolve(resolveVal)
                }
              }).catch(err => {
                if(err) {
                  resolveVal[i] = {
                    status: REJECTED,
                    value: err
                  }
                  if(++length === arr.length) resolve(resolveVal)
                }
              })
            } else {
              resolveVal[i] = {
                status: FULFILLED,
                value: res
              }
              if(++length === arr.length) resolve(resolveVal)
            }
          }
        })
      } else throw new Error('Promise.all must receive Array')
    } catch (error) {
      if(error instanceof Error) throw error
      return Promise.reject(error)
    }
  }

这些代码中还是有很多的bug

全部代码

const PANDING = 'panding'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class Promise {
  constructor(fn) {
    this.state = PANDING
    this.resolveVal = null
    this.rejectVal = null
    fn(this.resolve.bind(this), this.reject.bind(this))
  }
  resolve(value) {
      if(this.state !== PANDING) {
        return
      }
      this.state = FULFILLED
      this.resolveVal = value || this.resolveVal
  }
  reject(value) {
    let s = setInterval(() => {
        return
      }
      this.state = REJECTED
      this.rejectVal = value || this.rejectVal
  }
}
Promise.resolve = function(value) {
  if(value instanceof Promise) return value
  return new Promise((resolve) => {resolve(value)})
}
Promise.reject = function(value) {
  if(value instanceof Promise) return value
  return new Promise((resolve, reject) => {reject(value)})
}

Promise.all = function(arr) {
  try {
    if(arr instanceof Array) {
      return new Promise((resolve, reject) => {
        let resolveVal = []
        let isReject = false
        for(let i = 0; i < arr.length; i++) {
          let res = getReturn(arr[i])
          if(res instanceof Promise) {
              res.then(res1 => {
                resolveVal[i] = res1
              }).catch(err => {
                reject(err)
                isReject = true
              })
          }
          else resolveVal.push(res)
          if(isReject) break
        } 
        resolve(resolveVal)
      })
    } else throw new Error('Promise.all must receive Array')
  } catch (error) {
    if(error instanceof Error) throw error
    return Promise.reject(error)
  }
}
Promise.race = function(arr) {
  try {
    if(arr instanceof Array) {
      return new Promise((resolve, reject) => {
        if(arr.length === 0) resolve()
        for(let i = 0; i < arr.length; i++) {
          let res = getReturn(arr[i])
          if(res instanceof Promise) {
            res.then(res1 => {
              if(res1) {
                resolve(res)
              }
            }).catch(err => {
              if(err) {
                reject(err)
              }
            })
          } else {
            resolve(res)
          }
        }
      })
    } else throw new Error('Promise.all must receive Array')
  } catch (error) {
    if(error instanceof Error) throw error
    return Promise.reject(error)
  }
}
Promise.allSettled = function(arr) {
  try {
    if(arr instanceof Array) {
      return new Promise((resolve, reject) => {
        let resolveVal = []
        let length = 0
        if(arr.length === 0) resolve([])
        for(let i = 0; i < arr.length; i++) {
          let res = getReturn(arr[i])
          if(res instanceof Promise) {
            res.then(res1 => {
              if(res1) {
                resolveVal[i] = {
                  status: FULFILLED,
                  value: res1
                }
                if(++length === arr.length) resolve(resolveVal)
              }
            }).catch(err => {
              if(err) {
                resolveVal[i] = {
                  status: REJECTED,
                  value: err
                }
                if(++length === arr.length) resolve(resolveVal)
              }
            })
          } else {
            resolveVal[i] = {
              status: FULFILLED,
              value: res
            }
            if(++length === arr.length) resolve(resolveVal)
          }
        }
      })
    } else throw new Error('Promise.all must receive Array')
  } catch (error) {
    if(error instanceof Error) throw error
    return Promise.reject(error)
  }
}
Promise.prototype.then = function(resolve, reject) {
  let s = setInterval(() => {
    if(this.state !== PANDING) {
      s && clearInterval(s)
      try {
        let success = null
        let fail = null
        // 判断 传入参数的类型并绑定this
        typeof resolve === 'function' && (success = getReturn(resolve.call(this, this.resolveVal)))
        typeof reject === 'function' && (fail = getReturn(reject.call(this, this.rejectVal)))
        if(success) return Promise.resolve(success)
        if(fail) return Promise.resolve(fail)
        return this
      } catch (error) {
        return Promise.reject(error)
      }
    }
  },4)
}
Promise.prototype.catch = function(reject) {
  let s = setInterval(() => {
    if(this.state !== PANDING) {
      s && clearInterval(s)
      try {
        try {
          let fail = null
          typeof reject === 'function' && (fail = getReturn(reject.call(this, this.rejectVal)))
          if(fail) return Promise.resolve(fail)
          return this
        } catch (error) {
          return Promise.reject(error)
        }
      } catch (error) {
        return Promise.reject(error)
      }
    }
  },4)
}
Promise.prototype.finally = function(fn) {
  fn()
  return this
}

function getReturn(val) {
  if(typeof val === 'function') return val()
  return val
}