手撕 Promise 一文搞定

手撕 Promise,做 2 件事就够了:

  1. 了解 Promise 的行为 ,理解清晰后就能写出 then 方法
  2. 实现 then 方法,其他方法大多是它的衍生

其他方法,放到最后,很容易能用 then 衍生出来

Promise 行为概览

  1. 通过 new 调用,说明它是一个 class
  2. 构造时传入一个函数 executor
    • 同步执行 executor
    • 它有两个参数 resolvereject
    • 这两个参数负责:改变 Promise 状态、传递数据(分别叫 valuereason)
  3. 有 3 种状态,pendingfulfilledrejected
    • 初始状态是 pending
    • 只能 pending -> fulfilledpending -> rejected
    • 状态一旦改变后,无法继续调用 fulfilledrejected
  4. then 方法
    • 状态改变后,then 方法异步 接收 resolve/reject 的回调信息
    • then 内的回调函数实际是 微任务
    • 可以链式(多次)调用
    • 在状态敲定后调用,则立即执行
    • 立刻返回一个新 Promise(pending) 对象(下文称 p):
      • onFulfilledonRejected 将被异步执行,即使状态已被敲定
      • p 的行为取决于上一条的执行结果
        • 返回一个值:以该值作为兑现值
        • 无返回:以 undefined 作为兑现值(value)
        • 抛出错误:以错误作为拒绝值(reason)
        • 返回已兑现的Promise:以该 Promise 作为兑现值
        • 返回已拒绝的 Promise:以该 Promise 作为拒绝值
        • 返回待定(pending)的 Promise:保持待定,并在该 Promise 状态改变后以其值作为兑现/拒绝值

实现 then 方法

js 复制代码
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class Kromise {
  constructor(executor) {
    this.status = PENDING
    // value 和 reason 储存到静态属性里,方便 then 访问
    this.value = undefined
    this.reason = undefined
    // 支持链式调用,以数组保存回调
    this.onFulfilledFns = []
    this.onRejectedFns = []

    // value 传递值
    const resolve = (value) => {
      // 仅当 pending 时有效
      if (this.status === PENDING) {
        // 当兑现值是 Promise 时,等待他兑现/拒绝
        if (value instanceof Kromise) {
          value.then(resolve, reject)
          return
        }
        // Keep simple, 省略 thenable 部分

        /**
         * 执行回调,微任务:
         * 1、确保符合规范
         * 2、确保顺序执行 executor 时,不会立即执行 onFulfilled 导致 undefined 错误
         */
        queueMicrotask(() => {
          // 异步锁,防止异步流程中多次改变状态
          if (this.status !== PENDING) return

          // 将改变状态代码放到微任务中,为了确保不会过早敲定状态,导致 then 总执行敲定状态后的代码
          // 敲定状态
          this.status = FULFILLED
          // 储存value
          this.value = value
          this.onFulfilledFns.forEach(fn => fn(this.value))
        })
      }
    }

    const reject = (reason) => {
      if (this.status === REJECTED) {
        queueMicrotask(() => {
          if (this.status !== PENDING) return

          this.status = REJECTED
          this.reason = reason
          this.onRejectedFns.forEach(fn => fn(this.reason))
        })
      }
    }

    // 同步执行 executor,并传递参数
    executor(resolve, reject)
  }

  then(onFulfilled, onRejected) {
    // 返回 Promise,以支持链式调用
    return new Kromise((resolve, reject) => {
      // 如果在状态敲定后再执行 then,则立即执行回调
      if (this.status === FULFILLED && onFulfilled) {
        // 将值传递给返回的 p
        const value = onFulfilled(this.value)
        resolve(value)
      }
      if (this.status === REJECTED && onRejected) {
        const reason = onRejected(this.reason)
        reject(reason)
      }

      // 暂存,等状态改变(即 resolve/reject 执行)时才真正调用
      // try...catch 处理抛出错误的情况
      this.onFulfilledFns.push(() => executeFunctionWithErrorCatch(onFulfilled, this.value, resolve, reject))
      this.onRejectedFns.push(() => executeFunctionWithErrorCatch(onRejected, this.reason, resolve, reject))
    })
  }
}

function executeFunctionWithErrorCatch(fn, value, resolve, reject) {
  try {
    // 将自己的值,传递给返回的 Promise p
    const result = execFn(value)
    resolve(result)
  } catch(err) {
    reject(err)
  }
}

其他方法

catch

实际上是 then(undefined, onRejected) 的简写,但这里有一个边界情况要处理:

如何在then未处理onRejected的情况下,将该错误作为返回值传递给接下来链式调用的catch进行处理?

因为 then 仅注册 onFulfilled 回调时,返回的 p 无法将错误传递下去; 解决方法很简单,只需要提供一个默认的 onRejected 实现,保证错误传递即可

js 复制代码
  catch(onRejected) {
    return this.then(undefined, onRejected)
  }
  // 修改 then 的实现
  then(onFulfilled, onRejected) {
    // 返回 Promise,以支持链式调用
    return new Kromise((resolve, reject) => {
      // 如果在状态敲定后再执行 then,则立即执行回调
      if (this.status === FULFILLED && onFulfilled) {
        // 将值传递给返回的 p
        const value = onFulfilled(this.value)
        resolve(value)
      }
      if (this.status === REJECTED && onRejected) {
        const reason = onRejected(this.reason)
        reject(reason)
      }

      const defaultOnFulfilled = (value) => value
      const defaultOnRejected = (reason) => { throw reason }
      onFulfilled = onFulfilled || defaultOnFulfilled
      onRejected = onRejected || defaultOnRejected

      // 暂存,等状态改变(即 resolve/reject 执行)时才真正调用
      // try...catch 处理抛出错误的情况
      if (onFulfilled) this.onFulfilledFns.push(() => executeFunctionWithErrorCatch(onFulfilled, this.value, resolve, reject))
      if (onRejected) this.onRejectedFns.push(() => executeFunctionWithErrorCatch(onRejected, this.reason, resolve, reject))
    })
  }

finally

MDN: Promise实例的 finally() 方法用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数。它会立即返回一个等效的Promise对象,这可以允许我们链式调用其他 promise 方法

js 复制代码
finally(onFinally) {
  this.then(onFinally, onFinally)
}
相关推荐
温宇飞3 小时前
Web 异步编程
前端
腹黑天蝎座3 小时前
浅谈React19的破坏性更新
前端·react.js
东华帝君3 小时前
react组件常见的性能优化
前端
第七种黄昏3 小时前
【前端高频面试题】深入浏览器渲染原理:从输入 URL 到页面绘制的完整流程解析
前端·面试·职场和发展
angelQ3 小时前
前端fetch手动解析SSE消息体,字符串双引号去除不掉的问题定位
前端·javascript
Huangyi3 小时前
第一节:Flow的基础知识
android·前端·kotlin
林希_Rachel_傻希希3 小时前
JavaScript 解构赋值详解,一文通其意。
前端·javascript
Yeats_Liao3 小时前
Go Web 编程快速入门 02 - 认识 net/http 与 Handler 接口
前端·http·golang
金梦人生3 小时前
🔥Knife4j vs Swagger:Node.js 开发者的API文档革命!
前端·node.js