手写Promise,你也可以

291 阅读3分钟

在现代的 JavaScript 开发中,Promise 是处理异步操作的一种强大工具。本文将带你从零开始,手把手地实现一个简单的Promise,让你更深入地理解 Promise 的工作原理和实现机制。

首先什么是Promise?

Promise A+规范里,我们可以关注这一句话““promise” is an object or function with a then method whose behavior conforms to this specification.”,这句话是重点,我们后面需要用得到。

Promise 的基本结构

我们首先来定义 Promise 的基本结构。一个 Promise 实例具有以下特性:

  • 三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败),一旦 Promise 进入了 fulfilled 或 rejected 状态,它就是 settled(已定型)的,不会再改变状态。
  • 一个结果值,可以是任意类型的数据
  • 可以注册回调函数,处理异步操作的结果

话不多说,让我们直接上完整代码。

class MyPromise {
  // 定义状态常量
  #PENDING = 'PENDING'
  #FULFILLED = 'FULFILLED'
  #REJECTED = 'REJECTED'

  // 定义初始化状态
  #state = this.#PENDING

  // 保存返回值
  #result = undefined

  // 因为then可以链式调用,所以这里设置数组保存数据
  #handles = []

  constructor(executor) {
    const resolve = (value) => {
      this.#changeState(this.#FULFILLED, value)
    }

    const reject = (reason) => {
      this.#changeState(this.#REJECTED, reason)
    }

    try {
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  // 当状态改变的时候调用
  #changeState(state, result) {
    if (this.#state !== this.#PENDING) return
    this.#state = state
    this.#result = result
    this.#run()
  }

  // 判断是否是promise like
  #isPromiseLike(value) {
    return value !== null && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function'
  }

  // 把任务添加到微队列中
  #runMicroTask(func) {
    queueMicrotask(func)
  }

  // 抽取公共代码
  #runOnce(callback, resolve, reject) {
    this.#runMicroTask(() => {
      if(typeof callback === 'function') {
        try {
          const result = callback(this.#result)
          if(this.#isPromiseLike(result)) {
            result.then(resolve, reject)
          } else {
            resolve(result)
          }
        } catch (e) {
          reject(e)
        }
      } else {
        const fn = this.#state === this.#FULFILLED ? resolve : reject
        fn(this.#result)
      }
    })
  }

  // 执行then
  #run() {
    if (this.#state === this.#PENDING) return
    while (this.#handles.length) {
      const { onFulfilled, onRejected, resolve, reject } = this.#handles.shift()
      if(this.#state === this.#FULFILLED) {
        this.#runOnce(onFulfilled, resolve, reject)
        
      } else if(this.#state === this.#REJECTED) {
        this.#runOnce(onRejected, resolve, reject)
      }
    }
  }

  // then方法用于注册成功和失败的回调函数
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handles.push({
        onFulfilled,
        onRejected,
        resolve,
        reject
      })

      this.#run()
    })
  }
}

const p = new MyPromise((resolve, reject) => {
  reject('resolve')
})

p.then(res => {
  console.log('res', res);
  return 'first then'
}, err => {
  console.log('err', err);
}).then(res => {
  console.log('res', res);
}, err => {
  console.log('err', err);
})

// console.log(p);

总结

以上代码整体看起来并不复杂,大家应该都可以看的明白,这里我强调几点:

  • Promise.then需要放到微队列中
  • Promise.then方法返回的类型不确定,可以是一个函数,也可能不是函数(比如常量),也可能是一个Promise,所以这里的处理复杂一些
  • 无论是executor还是onFulfilled, onRejected,都有可能在执行中报错,所以我们需要使用try catch捕获
  • 为什么要在then方法和#changeState方法里都调用#run方法,是为了防止你在函数里面写了一个异步,比如以下代码:
const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 0)
})

以上就是一个简单的 JavaScript Promise 的手写实现。通过实现这个简易版本的 Promise,我们能更深入地理解 Promise 的原理和机制,也能更好地应用到实际项目中。希望本文对你有所帮助,谢谢阅读!