Skip to content

Promise

题目描述

实现一个自定义 Promise 类 (MyPromise),聚焦基础 Promise/A+ 行为:

  • 状态管理:pending → fulfilled | rejected 单向不可逆
  • 链式调用:then / catch / finally
  • 异步调度:回调通过微任务(优先 queueMicrotask)推迟执行
  • thenable 展开:递归解析符合 thenable 形态的对象
  • 错误传播与值透传:非函数处理器的穿透语义

⚠ 当前代码仅实现 static resolve / static reject,没有实现文档最初要求的 all / racefinally 行为与原生仍存在差异(不会等待其返回的 Promise)。本文件以下内容已“按代码真实行为”重写,并标注与规范的差异与可改进点。

核心知识点

状态机核心与内部字段

javascript
const PENDING = 'pending' // 等待中:初始状态
const FULFILLED = 'fulfilled' // 已成功:操作成功完成
const REJECTED = 'rejected' // 已拒绝:操作失败

内部私有字段:

字段含义
#state当前状态:pending / fulfilled / rejected
#result已决议结果(成功值或拒因)
#handlers待处理的回调队列(元素含 onFulfilled / onRejected / resolve / reject)

状态转换规则:

  1. 仅允许 pending → fulfilled | rejected
  2. 一旦确定不再改变(靠状态判定短路)
  3. 解析 thenable 时递归跟随直到得到普通值或错误

链式调用与值/错透传

then(onFulfilled, onRejected):立即将处理器对象压入 #handlers,随后调用 #run() 判断当前状态是否已决议:

  • 若仍是 pending:队列静置
  • 若已决议:同步触发 #run() 内的 while 循环,将每个处理排入微任务(真正用户回调仍是异步)

透传逻辑(在 #runOne 内):

  • 如果对应回调不是函数:
    • fulfilled → 直接 resolve(this.#result)
    • rejected → 直接 reject(this.#result)(形成错误冒泡)

链式创建:then 返回新的 MyPromise,由子 promise 的 executor 中捕获上层回调执行结果并调用其 resolve / reject

微任务调度策略

优先级(按代码实际顺序):

  1. queueMicrotask
  2. process.nextTick
  3. MutationObserver hack
  4. setTimeout 兜底(宏任务)

所有注册回调最终在 #runMicroTask 包裹的回调里执行,保证符合“异步执行”要求(A+:必须在当前执行栈完成后)。

thenable 解析与循环引用防护

  • isPromiseLike(x):判断对象/函数且含 then 方法
  • x === this:触发 TypeError('Chaining cycle detected')
  • 若是 thenable:调用其 then,成功分支递归 #resolvePromise,失败分支直接 #changeState(REJECTED,r)
  • 解析过程中依赖状态不可逆性防止多次决议;未显式实现“多次调用 then 防二次执行”锁,但状态切换后后续调用会被忽略(满足规范要求)

finally 实现特性

js
finally(cb) {
  return this.then(
    v => { cb && cb(); return v },
    e => { cb && cb(); throw e }
  )
}

差异:若 cb 返回一个 Promise,原生需等待其完成再继续链路;此实现直接忽略返回值,不等待。

静态方法行为

方法当前实现与原生差异
resolve(v)始终 new MyPromise(resolve => resolve(v))原生:若 v 已是同源 Promise 直接返回它(短路)
reject(r)new MyPromise((_, reject) => reject(r))与原生等价语义
all未实现——
race未实现——
allSettled未实现——
any未实现——

错误传播与捕获

  • executor 内同步异常 → try/catch → reject(e)
  • thenable then 执行抛错 → 捕获并拒绝
  • 用户回调抛错 → 在 #runOne try/catch 捕获并 reject
  • 非函数处理器时错误透传不被吞掉(保持冒泡)

与 PromiseA+ / 原生 Promise 的主要差异

维度当前实现差异说明/后果
finally 等待不等待返回的 Promise可能导致资源清理未完成就进入后续链
Promise.resolve始终包装失去同一个实例的快捷路径,轻微性能损耗
queue 触发时机then 已决议时同步调用 #run()行为允许(内部回调仍是异步)
长链内存handlers shift 一个一个出队O(n) shift 可能有性能影响(可改成索引递增)

真实行为快速示例

javascript
import MyPromise from './MyPromise'

MyPromise.resolve(1)
  .then(v => v + 1)
  .finally(() => new Promise(r => setTimeout(r, 100))) // 不会等待 100ms
  .then(v => console.log('=>', v)) // 立即输出 2(原生需等待 finally 完成)

new MyPromise((resolve) => {
  resolve(MyPromise.resolve(42)) // 嵌套 thenable 展开
}).then(v => console.log(v)) // 42

改进方向(按优先级)

  1. 实现 static all / race / allSettled / any
  2. finally 支持等待其返回值(若为 Promise)
  3. Promise.resolve 短路逻辑(若为 MyPromise 实例直接返回)
  4. handlers 队列改为索引遍历避免 shift 频繁移动
  5. 增加“未处理拒绝”检测(unhandled rejection 报告)
  6. 增加调用保护:thenable 的 then 只调用一次(标志位)
  7. 支持 MyPromise.deferred()(便于 A+ 官方测试套)
  8. 增加开发模式下的长堆栈追踪(链式错误排查)

微任务实现优先级

javascript
// 1. Node.js 环境:process.nextTick(最高优先级)
// 2. 浏览器环境:MutationObserver
// 3. 兜底方案:setTimeout(宏任务,不符合规范但可用)

function nextTick(fn) {
  if (typeof process !== 'undefined' && process.nextTick) {
    process.nextTick(fn)
  }
  else if (typeof MutationObserver !== 'undefined') {
    const observer = new MutationObserver(fn)
    const textNode = document.createTextNode('1')
    observer.observe(textNode, { characterData: true })
    textNode.data = '2'
  }
  else {
    setTimeout(fn, 0)
  }
}

then 方法的复杂处理逻辑

  • thenable 对象识别: 有 then 方法的对象
  • 循环引用检测: 防止 Promise 解析自身
  • 异常捕获: try-catch 包裹所有用户代码
  • 状态同步: 处理已决议 Promise 的立即执行

代码实现(当前版本)

javascript
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

export default class MyPromise {
  #state = PENDING
  #result = void 0
  #handlers = []

  constructor(executor) {
    let sealed = false // 只允许第一次生效

    const resolve = (x) => {
      if (sealed)
        return
      sealed = true
      this.#resolvePromise(x)
    }

    const reject = (r) => {
      if (sealed)
        return
      sealed = true
      this.#changeState(REJECTED, r)
    }

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

  // ===== 内部工具 =====
  isPromiseLike(value) {
    return (
      value !== null
      && (typeof value === 'object' || typeof value === 'function')
      && typeof value.then === 'function'
    )
  }

  #runMicroTask(fn) {
    if (typeof queueMicrotask === 'function') {
      queueMicrotask(fn)
    }
    else if (typeof process === 'object' && typeof process.nextTick === 'function') {
      process.nextTick(fn)
    }
    else if (typeof MutationObserver === 'function') {
      const ob = new MutationObserver(fn)
      const text = document.createTextNode('1')
      ob.observe(text, { characterData: true })
      text.data = '2'
    }
    else {
      setTimeout(fn, 0)
    }
  }

  #changeState(state, result) {
    if (this.#state !== PENDING)
      return
    this.#state = state
    this.#result = result
    this.#run()
  }

  // A+ 2.3: [[Resolve]](promise, x)
  #resolvePromise(x) {
    if (this.#state !== PENDING)
      return
    if (x === this) {
      this.#changeState(REJECTED, new TypeError('Chaining cycle detected'))
      return
    }

    if (this.isPromiseLike(x)) {
      // 跟随 thenable
      try {
        x.then(
          y => this.#resolvePromise(y), // 递归展开
          r => this.#changeState(REJECTED, r),
        )
      }
      catch (e) {
        this.#changeState(REJECTED, e)
      }
      return
    }

    // 普通值
    this.#changeState(FULFILLED, x)
  }

  #runOne(callback, resolve, reject) {
    this.#runMicroTask(() => {
      if (typeof callback !== 'function') {
        // 值透传/错透传
        if (this.#state === FULFILLED)
          resolve(this.#result)
        else reject(this.#result)
        return
      }
      try {
        const data = callback(this.#result)
        // 这里不直接展开,由子 promise 的 resolve 去执行 [[Resolve]]
        resolve(data)
      }
      catch (err) {
        reject(err)
      }
    })
  }

  #run() {
    if (this.#state === PENDING)
      return
    while (this.#handlers.length) {
      const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift()
      if (this.#state === FULFILLED) {
        this.#runOne(onFulfilled, resolve, reject)
      }
      else {
        this.#runOne(onRejected, resolve, reject)
      }
    }
  }

  // ===== 对外 API =====
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handlers.push({ onFulfilled, onRejected, resolve, reject })
      this.#run()
    })
  }

  catch(onRejected) {
    return this.then(undefined, onRejected)
  }

  finally(onFinally) {
    return this.then(
      (v) => {
        if (typeof onFinally === 'function')
          onFinally()
        return v
      },
      (e) => {
        if (typeof onFinally === 'function')
          onFinally()
        throw e
      },
    )
  }

  static resolve(value) {
    // 与原生差异:不会短路已是 MyPromise 的情况
    return new MyPromise(resolve => resolve(value))
  }

  static reject(reason) {
    return new MyPromise((_, reject) => reject(reason))
  }
}

规范差异详解

规范条目要求当前实现影响
A+ 2.2.4 回调必须异步回调排入微任务使用 queueMicrotask/降级符合
A+ 2.2.6 多次调用忽略依赖状态短路满足
A+ 2.3 解析 thenable递归调用 x.then
finally 等待等待返回值 Promise未等待可能过早继续
Promise.resolve 行为同实例短路未短路轻微性能差异
all / race提供未实现功能缺失
未处理拒绝事件可监控未提供调试不便

常见陷阱与排查提示

场景现象诊断建议
finally 中返回异步后续 then 立即执行标注语义差异或改造 finally
大量链式 then性能下降优化 handlers 结构(索引遍历)
thenable 实现不规范(多次调用)只第一次生效符合预期,无需额外处理
用户抛同步异常进入 rejected依赖 try/catch 已覆盖
忘记写返回值透传上一层值符合透传语义

改进示例

finally 规范化(等待返回 Promise)

javascript
finally(onFinally) {
  return this.then(
    v => MyPromise.resolve(typeof onFinally === 'function' && onFinally())
                  .then(() => v),
    e => MyPromise.resolve(typeof onFinally === 'function' && onFinally())
                  .then(() => { throw e })
  )
}

resolve 短路已有 Promise

javascript
static resolve(value) {
  if (value instanceof MyPromise) return value
  return new MyPromise(r => r(value))
}

索引遍历替换 shift(避免 O(n^2) 移动成本)

javascript
#run() {
  if (this.#state === PENDING) return
  let i = 0
  while (i < this.#handlers.length) {
    const h = this.#handlers[i++]
    this.#runOne(
      this.#state === FULFILLED ? h.onFulfilled : h.onRejected,
      h.resolve,
      h.reject,
    )
  }
  this.#handlers.length = 0
}

all 简易实现(示例)

javascript
static all(iterable) {
  return new MyPromise((resolve, reject) => {
    const results = []
    let count = 0
    let total = 0
    for (const item of iterable) {
      const idx = total++
      MyPromise.resolve(item).then(
        v => {
          results[idx] = v
          if (++count === total) resolve(results)
        },
        reject,
      )
    }
    if (total === 0) resolve([])
  })
}

练习与自测建议

目标建议用例
值/错透传Promise.resolve(1).then().then(v=>v+1)
thenable 展开手写 { then(res){ res(123) } }
循环引用const p = new MyPromise(r=>r()); p.then(()=>p)
finally 差异finally 返回 new Promise(r=>setTimeout(r,100))
多次决议忽略executor 中 resolve(1); resolve(2); reject(3)

总结

  1. 状态只改一次:#changeState + 早退判定
  2. 解析 thenable:递归 x.then(y => #resolvePromise(y))
  3. 回调异步:统一 #runMicroTask 排队
  4. 透传策略:非函数处理器直接走同向/反向
  5. finally 当前“不等待”→ 可改造成等待版本
  6. 缺失 all/race,可按示例扩展
  7. 队列结构可优化防 shift 性能损耗
  8. 可添加 unhandled rejection 监控提升可观测性

扩展思考

1. 支持取消的 Promise

javascript
class CancellablePromise extends MyPromise {
  constructor(executor) {
    let isCancelled = false
    let cancelCallback = null

    super((resolve, reject) => {
      const wrappedResolve = (value) => {
        if (!isCancelled)
          resolve(value)
      }

      const wrappedReject = (reason) => {
        if (!isCancelled)
          reject(reason)
      }

      cancelCallback = executor(wrappedResolve, wrappedReject)
    })

    this.cancel = () => {
      isCancelled = true
      if (typeof cancelCallback === 'function') {
        cancelCallback()
      }
    }
  }
}

// 使用示例
const cancellable = new CancellablePromise((resolve, reject) => {
  const timer = setTimeout(() => resolve('completed'), 1000)

  // 返回取消函数
  return () => clearTimeout(timer)
})

setTimeout(() => cancellable.cancel(), 500) // 取消执行

2. 支持重试的 Promise

javascript
class RetryablePromise extends MyPromise {
  static withRetry(promiseFactory, maxRetries = 3, delay = 1000) {
    return new MyPromise(async (resolve, reject) => {
      let lastError

      for (let attempt = 0; attempt <= maxRetries; attempt++) {
        try {
          const result = await promiseFactory()
          resolve(result)
          return
        }
        catch (error) {
          lastError = error

          if (attempt < maxRetries) {
            await new MyPromise(r => setTimeout(r, delay * 2 ** attempt))
          }
        }
      }

      reject(lastError)
    })
  }
}

// 使用示例
const fetchWithRetry = RetryablePromise.withRetry(
  () => fetch('/api/data'),
  3,
  1000,
)

3. 支持超时的 Promise

javascript
class TimeoutPromise extends MyPromise {
  static withTimeout(promise, timeout, timeoutMessage = 'Operation timeout') {
    return MyPromise.race([
      promise,
      new MyPromise((_, reject) => {
        setTimeout(() => reject(new Error(timeoutMessage)), timeout)
      }),
    ])
  }
}

// 使用示例
const promiseWithTimeout = TimeoutPromise.withTimeout(
  fetch('/api/slow-endpoint'),
  5000,
  'Request timeout after 5 seconds',
)

4. 支持进度的 Promise

javascript
class ProgressPromise extends MyPromise {
  constructor(executor) {
    const progressCallbacks = []

    super((resolve, reject) => {
      const progress = (value) => {
        progressCallbacks.forEach(callback => callback(value))
      }

      executor(resolve, reject, progress)
    })

    this.onProgress = (callback) => {
      progressCallbacks.push(callback)
      return this
    }
  }
}

// 使用示例
const uploadFile = new ProgressPromise((resolve, reject, progress) => {
  // 模拟文件上传
  let uploaded = 0
  const total = 100

  const interval = setInterval(() => {
    uploaded += 10
    progress((uploaded / total) * 100)

    if (uploaded >= total) {
      clearInterval(interval)
      resolve('Upload completed')
    }
  }, 100)
})

uploadFile
  .onProgress(percent => console.log(`Upload progress: ${percent}%`))
  .then(result => console.log(result))

5. 支持管道的 Promise

javascript
class PipelinePromise extends MyPromise {
  static pipeline(...functions) {
    return (initialValue) => {
      return functions.reduce(
        (promise, fn) => promise.then(fn),
        MyPromise.resolve(initialValue),
      )
    }
  }

  pipe(fn) {
    return this.then(fn)
  }
}

// 使用示例
const processData = PipelinePromise.pipeline(
  data => data.trim(),
  data => data.toUpperCase(),
  data => data.split(' '),
  data => data.filter(word => word.length > 3),
)

processData('  hello world programming  ').then(result =>
  console.log(result),
) // ['HELLO', 'WORLD', 'PROGRAMMING']

// 链式管道
MyPromise.resolve('initial data')
  .pipe(data => data.toUpperCase())
  .pipe(data => data.split(' '))
  .pipe(data => data.join('-'))
  .then(result => console.log(result)) // 'INITIAL-DATA'

内容基于 MIT 许可 | 保持节奏 · 持续积累