手写 Promise

180 阅读6分钟

前言

Promise 诞生于 ES2015(ES6),是 JS 主流的异步操作解决方案,也是前端面试过程中的必考题。作为前端开发,必须要能够熟练运用 Promise 并理解原理。

本文将一步一步分析 Promise 的特征与用法,并最终实现一个包含以下 API 的 Promise:

  • Promise.prototype.then()

  • Promise.prototype.catch()

  • Promise.prototype.finally()

  • Promise.all()

  • Promise.resolve()

  • Promise.reject()

具体实现步骤会在代码中通过注释说明。

为了避免代码重复,下面的每一步的实现过程都会忽略其他步骤的代码,只包含当前步骤的必要代码。完整代码将放在最后。

1. Promise 基本用法

1.1. 创建 Promise

const asyncFunc = new Promise((resolve, rejcet) => {
  Math.random() > 0.5 ? resolve('some data') : rejcet('some reason')
})

1.2. 处理 Promise

asyncFunc()
  .then(res => {
    console.log(res)
  })
  .catch(err => {
    console.log(err)
  })
  .finally(() => {
    console.log('finally')
  })

2. 实现 Promise

2.1. 声明状态

Promise 有三种状态:pendingfulfilledrejected,状态只能改变一次,之后任何时候都可以得到这个结果。

graph TD
PENDING --> FULFILLED
PENDING --> REJECTED

这里先将它们枚举出来,后续会大量用到:

// pending(待定,初始状态)
const PENDING = 'pending'

// fulfilled(已兑现,意味着操作成功)
const FULFILLED = 'fulfilled'

// rejected(已拒绝,意味着操作失败)
const REJECTED = 'rejected'

2.2. 实现 reject() 和 resolve()

根据 Promise 的用法我们知道,Promise 是一个构造函数,它接受一个普通函数作为参数,该函数的两个参数分别是 resolvereject,它们也是函数,用来改变 Promise 的状态。

class MyPromise {
  constructor(executor) {
    // 利用 try/catch 捕获错误
    try {
      // 执行传入的函数
      executor(this.resolve, this.reject)
    } catch (error) {
      // 执行错误直接 reject
      this.reject(error)
    }
  }
  
  // 设置初始状态为 PENDING
  status = PENDING
  // 存储 resolve 后返回的数据
  data = undefined
  // 存储 reject 后返回的原因
  reason = undefined
  
  // 成功
  resolve = data => {
    // 状态改变后就不能再变了
    if (this.status !== PENDING) return
    // 更改状态
    this.status = FULFILLED
    // 保存数据
    this.data = data
  }

  // 失败
  reject = reason => {
    // 状态改变后就不能再变了
    if (this.status !== PENDING) return
    // 更改状态
    this.status = REJECTED
    // 保存原因
    this.reason = reason
  }
}

2.3. 实现 .then()

.then() 方法是 Promise 的核心之一,异步操作的成功或失败,都可以通过传递给 .then() 方法的回调函数进行处理。并且它会继续返回一个 Promise 对象,这样可以通过多次调用 .then() 添加多个回调函数,它们会按照插入的顺序执行,形成链式调用(chaining)

class MyPromise {
  // 存储 resolve 的回调函数
  fulfilledCallback = []
  // 存储 reject 的回调函数
  rejectedCallback = []

  // 成功
  resolve = data => {
    // 依次调用成功回调
    while (this.fulfilledCallback.length) {
      this.fulfilledCallback.shift()(this.data)
    }
  }
  
  // 失败
  reject = reason => {
    // 依次调用失败回调
    while (this.rejectedCallback.length) {
      this.rejectedCallback.shift()(this.reason)
    }
  }
  
  // .then()
  then(onResolved = data => data /*设置默认的成功回调 */, onRejected) {
    // 创建一个新的 Promise 并 return,以供链式调用
    const promise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 异步执行,用来获取新的 promise
        setTimeout(() => {
          try {
            const value = onResolved(this.data)
            // 判断返回值是普通值还是 Promise
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)

        return
      }

      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            const value = onRejected(this.reason)
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)

        return
      }

      // 将回调函数存入数组
      this.fulfilledCallback.push(() => {
        setTimeout(() => {
          try {
            let value = onResolved(this.data)
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      })

      // 将回调函数存入数组
      this.rejectedCallback.push(() => {
        setTimeout(() => {
          try {
            let value = onRejected(this.reason)
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      })
    })

    return promise
  }
}

2.4. 实现 .catch()

实现 .then() 之后,.catch() 就会简单很多。因为 .catch() 只是没有给 fulfilled 状态预留参数位置的 .then() 而已,所以这里直接返回一个没有成功回调函数的 .then() 即可。

class MyPromise {
  // .catch()
  catch(onRejected) {
    // .catch() 只是没有给 fulfilled 状态预留参数位置的 .then()
    return this.then(undefined, onRejected)
  }
}

2.5. 实现 .finally()

有些业务需要在不管成功还是失败时都进行,为了避免了代码重复,所以有了 .finally() 方法,无论是 Promise 最终是 fulfilled 还是 rejected 状态,.finally() 都会执行,但区别在于 .finally() 的回调函数中没有参数。

class MyPromise {
  // .finally()
  finally(callback) {
    // 因为 .then 也会在两种状态下都执行,所以这里复用即可,并且在两种状态下都执行同一个无参数的回调函数
    return this.then(
      data => {
        return MyPromise.resolve(callback()).then(() => data)
      },
      err => {
        return MyPromise.resolve(callback()).then(() => {
          throw err
        })
      }
    )
  }
}

2.6. 实现 Promise.all()

.then().catch().finally() 不同,Promise.all() 是一个静态方法,用于集合多个 Promise 的返回结果。

用法:

const asyncFuncFirst = new Promise(() => { /* do something */ })
const asyncFuncSecond = new Promise(() => { /* do something */ })
const asyncFuncThird = new Promise(() => { /* do something */ })

Promise.all([asyncFuncFirst, asyncFuncSecond, asyncFuncThird])
  .then(([resFirst, resSecond, resThird]) => {
    /* do something */
  })
  .catch(err => {
    /* do something */
  })
  .finally(() => {
    /* do something */
  })

实现:

class MyPromise {
  static all(funcs) {
    // 记录执行次数,用户判断是否已经全部执行
    let times = 0
    // 保存执行结果
    let result = []
    // 会返回一个 Promise
    return new MyPromise((resolve, reject) => {
      // 记录结果
      function addData(key, value) {
        times++
        result[key] = value
        times === funcs.length && resolve(result)
      }

      // 依次执行,并保存执行结果
      funcs.forEach((element, index) => {
        // 判断元素是否为 Promise 对象
        element instanceof MyPromise
          ? element.then(
              data => addData(index, data),
              err => reject(err) // 任何一个 Promise 对象的 reject 被执行都会立即 reject()
            )
          : addData(index, element) // 非 promise 的元素将被直接放在返回数组中
      })
    })
  }
}

2.7. 实现 Promise.resolve()

Promise.resolve() 方法返回一个以给定值解析后的 Promise 对象。

class MyPromise {
  // Promise.resolve()
  static resolve(value) {
    // 返回一个以给定值解析后的 Promise 对象
    return value instanceof MyPromise
      ? value
      : new MyPromise(resolve => resolve(value))
  }
}

2.8. 实现 Promise.reject()

Promise.reject() 方法返回一个带有拒绝原因的 Promise 对象。

class MyPromise {
  //...
  // Promise.reject()
  static reject(error) {
    return new MyPromise((resolve, reject) => {
      reject(error)
    })
  }
  // ...
}

3. 完整代码

// 声明 Promise 的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

// 以构造函数的形式实现
class MyPromise {
  constructor(executor) {
    // 利用 try/catch 捕获错误
    try {
      // 执行传入的函数
      executor(this.resolve, this.reject)
    } catch (error) {
      // 执行错误直接 reject
      this.reject(error)
    }
  }

  // 设置初始状态为 PENDING
  status = PENDING
  
  // 存储 resolve 后返回的数据
  data = undefined
  // 存储 reject 后返回的原因
  reason = undefined
  
  // 存储 resolve 的回调函数列表
  fulfilledCallback = []
  // 存储 reject 的回调函数列表
  rejectedCallback = []

  // 成功
  resolve = data => {
    // 状态改变后就不能再变了
    if (this.status !== PENDING) return
    // 更改状态
    this.status = FULFILLED
    // 保存数据
    this.data = data
    // 依次调用成功回调
    while (this.fulfilledCallback.length) {
      this.fulfilledCallback.shift()(this.data)
    }
  }

  // 失败
  reject = reason => {
    // 状态改变后就不能再变了
    if (this.status !== PENDING) return
    // 更改状态
    this.status = REJECTED
    // 保存原因
    this.reason = reason
    // 依次调用失败回调
    while (this.rejectedCallback.length) {
      this.rejectedCallback.shift()(this.reason)
    }
  }

  // then:处理 resolve 和 reject
  then(onResolved = data => data /*设置默认的成功回调 */, onRejected) {
    // 创建一个新的 Promise 并 return,以供链式调用
    const promise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 转换为 异步执行,用来获取 新的 promise
        setTimeout(() => {
          try {
            const value = onResolved(this.data)
            // 判断返回值是普通值还是 Promise
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)

        return
      }

      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            const value = onRejected(this.reason)
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)

        return
      }

      // 将回调函数存入数组
      this.fulfilledCallback.push(() => {
        setTimeout(() => {
          try {
            let value = onResolved(this.data)
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      })

      // 将回调函数存入数组
      this.rejectedCallback.push(() => {
        setTimeout(() => {
          try {
            let value = onRejected(this.reason)
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      })
    })

    return promise
  }

  // .catch()
  catch(onRejected) {
    // 事实上 .catch() 只是没有给 fulfilled 状态预留参数位置的 .then()
    return this.then(undefined, onRejected)
  }

  // .finally()
  finally(callback) {
    // 因为 .then 也会在两种状态下都执行,所以这里复用即可,并且在两种状态下都执行同一个无参数的回调函数
    return this.then(
      data => {
        return MyPromise.resolve(callback()).then(() => data)
      },
      err => {
        return MyPromise.resolve(callback()).then(() => {
          throw err
        })
      }
    )
  }

  // Promise.all()
  static all(iterable) {
    // 记录执行次数,用户判断是否已经全部执行
    let times = 0
    // 保存执行结果
    let result = []
    // 返回一个 Promise
    return new MyPromise((resolve, reject) => {
      // 记录结果
      function addData(key, value) {
        times++
        result[key] = value
        times === iterable.length && resolve(result)
      }
      // 依次执行,然后将结果保存到数组中
      iterable.forEach((element, index) => {
        // 判断元素是否为 Promise 对象
        element instanceof MyPromise
          ? element.then(
              data => addData(index, data),
              err => reject(err) // 任何一个 Promise 对象的 reject 被执行都会立即 reject()
            )
          : addData(index, element) // 非 promise 的元素将被直接放在返回数组中
      })
    })
  }

  // Promise.resolve()
  static resolve(value) {
    // 返回一个以给定值解析后的 Promise 对象
    return value instanceof MyPromise ? value : new MyPromise(resolve => resolve(value))
  }

  // Promise.reject()
  static reject(error) {
    return new MyPromise((resolve, reject) => {
      reject(error)
    })
  }
}

// 判断 Promise 的返回值类型
function resolvePromise(promise, value, resolve, reject) {
  // 循环调用报错
  if (promise === value) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  // 如果是 Promise 对象
  if (value instanceof MyPromise) {
    value.then(resolve, reject)
  } else {
    resolve(value)
  }
}

export default MyPromise