JS:手写实现 Promise

前言

JavaScript 中的 Promise 诞生于 ES2015(ES6),是当下前端开发中特别流行的一种异步操作解决方案,也几乎是前端面试过程中的必考题。如果能够熟练运用 Promise,并深入理解 Promise 的运行原理,想必不论是在实际开发中还是在面试过程中,都能如鱼得水。

本篇文章我们就来一步步分析 Promise 的特征与用法,并最终实现一个包含以下 API 的 Promise:

  • Promise.prototype.then()

  • Promise.prototype.catch()

  • Promise.prototype.finally()

  • Promise.all()

  • Promise.resolve()

  • Promise.reject()


TIPS:

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

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

完整代码将会放在最后。


开始之前先回顾一下 Promise 的基本用法

  • 创建 Promise

    let asyncFunc = new Promise((resolve, rejcet) => {
      Math.random() > 0.5 ? resolve('fulfilled') : rejcet('rejected')
    })
    
  • 处理 Promise 返回的状态(完成或者失败)

    asyncFunc.then(
      res => {
        console.log(res)
      },
      err => {
        console.log(err)
      }
    )
    

第一步:声明 Promise 的三种状态

我们知道 Promise 有三种状态,他们不受外界影响,而且一旦状态改变,就不会再变,任何时候都可以得到这个结果。这里先将它们枚举出来,后续会大量用到:

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。

    const PENDING = 'pending'
    
  • 已兑现(fulfilled): 意味着操作成功完成。

    const FULFILLED = 'fulfilled'
    
  • 已拒绝(rejected): 意味着操作失败。

    const REJECTED = 'rejected'
    

第二步:实现 reject() 和 resolve()

ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。它接受一个函数作为参数,该函数的两个参数分别是 resolvereject。它们也是函数。

// 以构造函数的形式实现
class MyPromise {
  constructor(executor) {
    // 利用 try/catch 捕获错误
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }
  // 定义 Promise 初始状态为 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
  }
}

第三步:实现 .then()

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

class MyPromise {
  // resolve 的回调函数列表
  successCallback = []
  // reject 的回调函数列表
  failureCallback = []
  // 成功
  resolve = data => {
    // 依次调用成功回调
    while (this.successCallback.length) {
      this.successCallback.shift()(this.data)
    }
  }
  // 失败
  reject = reason => {
    // 依次调用失败回调
    while (this.failureCallback.length) {
      this.failureCallback.shift()(this.reason)
    }
  }
  // .then():处理 resolve 和 reject
  then(onResolved = data => data /*设置默认的成功回调 */, onRejected) {
    // 创建一个新的 Promise 并 return,以供链式调用
    let promise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 转换为 异步执行,用来获取 新的 promise
        setTimeout(() => {
          try {
            let value = onResolved(this.data)
            // 判断返回值是普通值还是 Promise
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let value = onRejected(this.reason)
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else {
        // 将回调函数存入数组中等待被执行
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let value = onResolved(this.data)
              resolvePromise(promise, value, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
        // 将回调函数存入数组中等待被执行
        this.failureCallback.push(() => {
          setTimeout(() => {
            try {
              let value = onRejected(this.reason)
              resolvePromise(promise, value, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
      }
    })
    return promise
  }
}

第四步:实现 .catch()

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

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

第五步:实现 .finally()

有时候我们会在成功或失败执行相同的函数,为了避免了同样的语句需要在中各写一次的情况,所以有了.finally() 方法。也就是说,在 Promise 的结果无论是 fulfilled 或者是 rejected,.finally() 都会执行指定的回调函数。但区别于 .then().catch().finally() 的回调函数中不接收任何参数。

class MyPromise {
  // .finally()
  finally(callback) {
    return this.then(
      data => {
        return MyPromise.resolve(callback().then(() => data))
      },
      err => {
        return MyPromise.resolve(callback()).then(() => {
          throw err
        })
      }
    )
  }
}

第六步:实现 Promise.all()

Promise.all() 方法主要用于集合多个 Promise 的返回结果。

class MyPromise {
  // Promise.all()
  static all(iterable) {
    // 记录执行次数
    let times = 0
    // 保存执行结果
    let result = []
    // Promise.all() 会返回一个 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()

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

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

第八步:实现 Promise.reject()

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

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

完整代码

// 首先,我们声明它的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

// 以构造函数的形式实现
class MyPromise {
  constructor(executor) {
    // 利用 try/catch 捕获错误
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }
  // 定义 Promise 初始状态为 PENDING
  status = PENDING
  // resolve 后返回的数据
  data = undefined
  // reject 后返回的原因
  reason = undefined
  // resolve 的回调函数列表
  successCallback = []
  // reject 的回调函数列表
  failureCallback = []
  // 成功
  resolve = data => {
    // 一旦状态改变,就不能再变
    if (this.status !== PENDING) return
    // 更改状态
    this.status = FULFILLED
    // 保存数据
    this.data = data
    // 依次调用成功回调
    while (this.successCallback.length) {
      this.successCallback.shift()(this.data)
    }
  }
  // 失败
  reject = reason => {
    // 一旦状态改变,就不能再变
    if (this.status !== PENDING) return
    // 更改状态
    this.status = REJECTED
    // 保存原因
    this.reason = reason
    // 依次调用失败回调
    while (this.failureCallback.length) {
      this.failureCallback.shift()(this.reason)
    }
  }
  // then:处理 resolve 和 reject
  then(onResolved = data => data /*设置默认的成功回调 */, onRejected) {
    // 创建一个新的 Promise 并 return,以供链式调用
    let promise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 转换为 异步执行,用来获取 新的 promise
        setTimeout(() => {
          try {
            let value = onResolved(this.data)
            // 判断返回值是普通值还是 Promise
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let value = onRejected(this.reason)
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else {
        // 将回调函数存入数组
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let value = onResolved(this.data)
              resolvePromise(promise, value, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
        // 将回调函数存入数组
        this.failureCallback.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) {
    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.all() 会返回一个 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 对象
  if (value instanceof MyPromise) {
    value.then(resolve, reject)
  } else {
    resolve(value)
  }
}

module.exports = MyPromise

你可能感兴趣的:(JS:手写实现 Promise)