技术知识文章集合TECHNICAL ARCHIVE · 807 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTJS

一起学习造轮子(一):从零开始写一个符合 Promises/A+ 规范的 Promise

所属分类
JavaScript
文件格式
Markdown
原始路径
JavaScript/74-一起学习造轮子(一):从零开始写一个符合Promises A+规范的promise
本文目录14 个章节
  1. 前言:为什么要自己实现 Promise
  2. 一、Promises/A+ 与 ECMAScript Promise 的范围
  3. 二、基础版本:先保存一个成功/失败回调
  4. 三、支持同步 executor:回调不能同步执行
  5. 四、支持三种状态
  6. 五、支持多个观察者和早期“链式”写法
  7. 六、then 返回新 Promise:连接异步流程
  8. 七、Promise Resolution Procedure:解析 handler 返回值
  9. 八、最终教学实现:状态、锁、thenable 和链式调用
  10. 九、Promises/A+ 2.3 条款逐项对应
  11. 十、all、race、resolve 和 reject
  12. 十一、promisify:把错误优先回调转换成 Promise
  13. 十二、建议的回归测试
  14. 总结

一起学习造轮子(一):从零开始写一个符合 Promises/A+ 规范的 Promise

Category(分类): JavaScript Status: 已整理

本文保留原文“从基础版本逐步实现 Promise”的学习路线、文件读取案例和 Promises/A+ 条款分析。原文代码中的复制按钮/解读提示等抓取残留、状态竞态、thenable 处理和静态方法边界已修正。

重要范围说明:Promises/A+ 主要规范 then 方法和 Promise Resolution Procedure,并不规范构造器、静态 all/race 方法,也不要求实现必须使用原生 Promise 的 microtask。本文的最终实现是教学用 MyPromise,不是原生 Promise 的完整 polyfill。

原文属于“造轮子”系列,配套源码曾托管在 MyWheel。原文的 4 张配图已下载到同目录 images 文件夹;截图是历史材料,不能代替当前规范文字。

前言:为什么要自己实现 Promise

Promise 是处理异步流程的一种抽象。它把“现在还没有结果、以后会成功或失败”的状态封装起来,并允许通过 then 组织后续操作。ES2015 将 Promise 纳入 ECMAScript 标准后,浏览器和 Node.js 都提供了原生实现。

自己实现一个简化版本仍然有学习价值,因为它可以帮助理解:

  • pending、fulfilled、rejected 三种状态以及状态不可逆。
  • then 为什么要返回一个新的 Promise,才能形成真正的链。
  • handler 返回普通值、Promise 或 thenable 时,后继 Promise 如何解析。
  • 错误如何穿过链条,以及为什么 catch 是 then(null, onRejected) 的语法糖。
  • A+ Resolution Procedure 与 ECMAScript 原生构造器/静态方法的边界。

生产代码应直接使用原生 Promise。下面的阶段代码故意展示演进过程,只有最后的完整实现适合拿来做教学实验,而且仍不应替代标准实现。

原文配图:项目源码目录

一、Promises/A+ 与 ECMAScript Promise 的范围

主题Promises/A+当前 ECMAScript Promise
then 返回新 Promise规范核心支持
handler 异步执行要求调用栈只剩平台代码使用 Promise Jobs/microtasks
2.3 Resolution Procedure规范核心以规范内部操作实现
构造器 new Promise(executor)不负责规定有 alreadyResolved 和 thenable 吸收
all、race、resolve、reject不属于 A+ 核心静态方法接收 iterable 并处理普通值
catch、finally、allSettled、any不属于 A+ 核心由 ECMAScript 提供

因此,“通过 promises-aplus-tests”只能说明测试覆盖范围内的 then 适配符合 A+,不能证明构造器、thenable、all、race 或 promisify 与原生 Promise 完全相同。

二、基础版本:先保存一个成功/失败回调

目标

  1. 可以创建 Promise 对象。
  2. executor 成功时调用成功回调,失败时调用失败回调。
  3. 先展示最少代码,但明确它还没有状态、链式返回和完整错误处理。

原文的第一版把未注册的回调直接调用,会在同步 resolve 时访问 null。下面保留它的教学意图,但用可选调用让示例不会因为演示而直接崩溃:

function BasicPromise(executor) {
  const self = this
  self.value = undefined
  self.reason = undefined
  self.onFulfilled = null
  self.onRejected = null

  function resolve(value) {
    self.value = value
    self.onFulfilled?.(value)
  }

  function reject(reason) {
    self.reason = reason
    self.onRejected?.(reason)
  }

  executor(resolve, reject)
}

BasicPromise.prototype.then = function (onFulfilled, onRejected) {
  this.onFulfilled = onFulfilled
  this.onRejected = onRejected
  return this
}

这个版本有意保留了几个缺陷:只能保存一个成功和一个失败回调;同步 resolve 发生在 then 注册之前时,结果不会被补发;返回 this 也不是原生 Promise 的链式语义。它只能作为起点,不能当作 Promise 实现使用。

三、支持同步 executor:回调不能同步执行

原生 Promise 的 executor 会立即执行,但 then 注册的 handler 不会在当前调用栈中同步执行:

const events = []

new Promise((resolve) => {
  resolve('done')
}).then(() => events.push('then'))

events.push('after')

queueMicrotask(() => {
  console.log(events) // ['after', 'then']
})

A+ 只要求 handler 调用时执行上下文栈已经清空,并没有规定必须使用 microtask。原文使用 setTimeout 达到“晚于当前栈”的效果;更接近现代原生 Promise 的教学调度可以优先使用 queueMicrotask:

const enqueueJob = typeof queueMicrotask === 'function'
  ? queueMicrotask
  : (job) => setTimeout(job, 0)

function schedule(onFulfilled, value) {
  enqueueJob(() => onFulfilled?.(value))
}

setTimeout(fn, 0) 是一个后续 task,而 queueMicrotask(fn) 是当前 task 结束后的 microtask。两者都能满足“不是同步调用”的最低要求,但观察到的顺序不相同。

四、支持三种状态

Promise 的状态通常写成:

  • pending:尚未确定结果。
  • fulfilled:成功完成,并保存 fulfillment value。
  • rejected:失败完成,并保存 rejection reason。

状态只能从 pending 变为 fulfilled 或 rejected,不能从 fulfilled 变回 rejected,也不能从 rejected 变回 fulfilled。

resolved 不一定等于 fulfilled

原生 Promise 还要区分 resolved(已锁定/已解析) 和 settled(已落定):

const inner = new Promise((resolve) => {
  setTimeout(() => resolve('inner value'), 100)
})

const outer = new Promise((resolve, reject) => {
  resolve(inner) // outer 已经锁定为跟随 inner,但此刻还未 fulfilled
  reject(new Error('ignored')) // 第一次 resolve 后被忽略
})

outer.then(console.log) // 约 100ms 后输出 inner value

实现时必须把“第一次 resolve/reject 调用立即锁定”与“最终状态写入”分开。只在 setTimeout 回调里检查 status === pending 是错误的,因为快速连续调用可能同时排队:

// 错误思路:两个调用都在定时器执行前看到 pending
resolve(1)
reject(2)

正确实现需要 alreadyResolved 一次性锁,并在内部 fulfill/rejectInternal 中再次保护最终状态。

五、支持多个观察者和早期“链式”写法

如果一个 Promise 允许多个 then,回调需要保存为数组:

const onFulfilledCallbacks = []
const onRejectedCallbacks = []

onFulfilledCallbacks.push(handler)
for (const callback of onFulfilledCallbacks) {
  callback(value)
}

原文第四节在数组基础上返回 this,这样可以写出:

promise.then(f1).then(f2).then(f3)

但这仍然是同一个 Promise 上的回调注册,不是真正的原生链。真正的链要求:

  1. 每次 then 都创建一个新的 Promise。
  2. 当前 handler 返回的普通值决定下一个 Promise fulfilled 的值。
  3. handler 返回的 Promise/thenable 被下一个 Promise 吸收。
  4. handler 抛出的异常让下一个 Promise rejected。

六、then 返回新 Promise:连接异步流程

原文使用 bridgePromise 解释这一点非常有价值。执行:

p.then(f1).then(f2).then(f3)

可以把它理解成:

  1. p.then(f1) 创建 p2,f1 的结果决定 p2。
  2. p2.then(f2) 创建 p3,f2 的结果决定 p3。
  3. p3.then(f3) 创建 p4,f3 的结果决定 p4。
  4. 如果 f1 返回一个 pending Promise,p2 会跟随它,后续不会提前执行。

文件读取案例

原文使用 Node.js callback 风格的 fs.readFile 演示串行读取,这是 2018 年常见的写法:

const fs = require('node:fs')

function readFile(path) {
  return new Promise((resolve, reject) => {
    fs.readFile(path, 'utf8', (error, data) => {
      if (error) reject(error)
      else resolve(data)
    })
  })
}

readFile('./file/1.txt')
  .then((content) => {
    console.log(content)
    return readFile('./file/2.txt')
  })
  .then((content) => {
    console.log(content)
    return readFile('./file/3.txt')
  })
  .then(console.log)
  .catch(console.error)

当前 Node.js 优先使用 node:fs/promises:

const { readFile } = require('node:fs/promises')

readFile('./file/1.txt', 'utf8')
  .then((content) => readFile('./file/2.txt', 'utf8').then((next) => [content, next]))
  .then(console.log)
  .catch(console.error)

七、Promise Resolution Procedure:解析 handler 返回值

A+ 2.3 的核心是 [[Resolve]](promise, x)。当 handler 返回 x 时,需要按以下顺序处理:

  1. 如果 x 就是待解析的下一个 Promise,拒绝并抛出 TypeError,避免直接循环引用。
  2. 如果 x 是对象或函数,读取一次 x.then。
  3. 读取 then 时抛异常,就以该异常拒绝。
  4. 如果 then 不是函数,把 x 当作普通值 fulfill。
  5. 如果 then 是函数,以 x 为 this 调用它。
  6. thenable 的第一次 resolve/reject 获胜;resolve 得到的 y 继续递归解析。
  7. then 调用已经完成后再次抛出的异常被忽略。

因此,不能只用 x instanceof MyPromise 判断 Promise。跨 iframe 的原生 Promise、其他 Promise 实现和普通 thenable 都可能具有不同构造函数,但只要遵循 then 协议就应该能够互操作。

const thenable = {
  then(resolve) {
    resolve({
      then(resolveAgain) {
        resolveAgain(42)
      },
    })
  },
}

Promise.resolve(thenable).then(console.log) // 42

A+ 规范允许实现选择宏任务或微任务来满足异步要求;下面最终实现使用 queueMicrotask 调度 handler,但它仍是教学实现。

原文配图:bridgePromise 链式关系

八、最终教学实现:状态、锁、thenable 和链式调用

下面的实现集中修复了原文最终版本的关键问题:

  • resolve/reject 第一次调用立即锁定。
  • 构造器 executor 同步抛错会被捕获;如果此前已经 resolve/reject,则异常被忽略。
  • 直接 resolve 普通 thenable 会被递归吸收。
  • 自解析会 rejected。
  • 每个 handler 只执行一次,settle 后清空队列,减少闭包保留。
  • 非函数 handler 使用 identity/thrower 默认行为。
  • handler 返回值统一交给 Resolution Procedure。
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

const enqueueJob = typeof queueMicrotask === 'function'
  ? queueMicrotask
  : (job) => setTimeout(job, 0)

function isObjectOrFunction(value) {
  return (typeof value === 'object' && value !== null) || typeof value === 'function'
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    reject(new TypeError('Promise cannot resolve to itself'))
    return
  }

  if (!isObjectOrFunction(x)) {
    resolve(x)
    return
  }

  let then
  try {
    then = x.then
  } catch (error) {
    reject(error)
    return
  }

  if (typeof then !== 'function') {
    resolve(x)
    return
  }

  let called = false
  try {
    then.call(
      x,
      (value) => {
        if (called) return
        called = true
        resolvePromise(promise2, value, resolve, reject)
      },
      (reason) => {
        if (called) return
        called = true
        reject(reason)
      }
    )
  } catch (error) {
    if (called) return
    called = true
    reject(error)
  }
}

class MyPromise {
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new TypeError('MyPromise executor must be a function')
    }

    this._state = PENDING
    this._value = undefined
    this._handlers = []

    let alreadyResolved = false

    const fulfill = (value) => {
      if (this._state !== PENDING) return
      this._state = FULFILLED
      this._value = value
      this._flushHandlers()
    }

    const rejectInternal = (reason) => {
      if (this._state !== PENDING) return
      this._state = REJECTED
      this._value = reason
      this._flushHandlers()
    }

    const resolve = (value) => {
      if (alreadyResolved) return
      alreadyResolved = true

      if (value === this) {
        rejectInternal(new TypeError('Promise cannot resolve to itself'))
        return
      }

      resolvePromise(this, value, fulfill, rejectInternal)
    }

    const reject = (reason) => {
      if (alreadyResolved) return
      alreadyResolved = true
      rejectInternal(reason)
    }

    try {
      executor(resolve, reject)
    } catch (error) {
      if (!alreadyResolved) {
        alreadyResolved = true
        rejectInternal(error)
      }
    }
  }

  _flushHandlers() {
    if (this._state === PENDING) return

    const handlers = this._handlers
    this._handlers = []
    for (const handler of handlers) {
      enqueueJob(() => this._runHandler(handler))
    }
  }

  _runHandler(handler) {
    const callback = this._state === FULFILLED
      ? handler.onFulfilled
      : handler.onRejected

    if (callback === null) {
      const passthrough = this._state === FULFILLED
        ? (value) => handler.resolve(value)
        : (reason) => handler.reject(reason)
      passthrough(this._value)
      return
    }

    try {
      const result = callback(this._value)
      resolvePromise(handler.promise, result, handler.resolve, handler.reject)
    } catch (error) {
      handler.reject(error)
    }
  }

  then(onFulfilled, onRejected) {
    const fulfilledHandler = typeof onFulfilled === 'function' ? onFulfilled : null
    const rejectedHandler = typeof onRejected === 'function' ? onRejected : null

    let nextResolve
    let nextReject
    const nextPromise = new MyPromise((resolve, reject) => {
      nextResolve = resolve
      nextReject = reject
    })

    const handler = {
      onFulfilled: fulfilledHandler,
      onRejected: rejectedHandler,
      promise: nextPromise,
      resolve: nextResolve,
      reject: nextReject,
    }

    if (this._state === PENDING) {
      this._handlers.push(handler)
    } else {
      enqueueJob(() => this._runHandler(handler))
    }

    return nextPromise
  }

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

  finally(onFinally) {
    const callback = typeof onFinally === 'function' ? onFinally : () => undefined

    return this.then(
      (value) => MyPromise.resolve(callback()).then(() => value),
      (reason) => MyPromise.resolve(callback()).then(() => {
        throw reason
      })
    )
  }

  static resolve(value) {
    if (value instanceof this && value.constructor === this) {
      return value
    }
    return new this((resolve) => resolve(value))
  }

  static reject(reason) {
    return new this((resolve, reject) => reject(reason))
  }

  static all(iterable) {
    const Ctor = this
    return new Ctor((resolve, reject) => {
      let values
      try {
        values = Array.from(iterable)
      } catch (error) {
        reject(error)
        return
      }

      const results = new Array(values.length)
      if (values.length === 0) {
        resolve(results)
        return
      }

      let remaining = values.length
      values.forEach((value, index) => {
        Ctor.resolve(value).then(
          (result) => {
            results[index] = result
            remaining -= 1
            if (remaining === 0) resolve(results)
          },
          reject
        )
      })
    })
  }

  static race(iterable) {
    const Ctor = this
    return new Ctor((resolve, reject) => {
      let values
      try {
        values = Array.from(iterable)
      } catch (error) {
        reject(error)
        return
      }

      for (const value of values) {
        Ctor.resolve(value).then(resolve, reject)
      }
      // 空 iterable 会保持 pending,这是原生 Promise.race 的语义。
    })
  }

  static allSettled(iterable) {
    const Ctor = this
    return new Ctor((resolve, reject) => {
      let values
      try {
        values = Array.from(iterable)
      } catch (error) {
        reject(error)
        return
      }

      Ctor.all(values.map((value) => Ctor.resolve(value).then(
        (result) => ({ status: 'fulfilled', value: result }),
        (reason) => ({ status: 'rejected', reason })
      ))).then(resolve, reject)
    })
  }

  static promisify(fn, thisArg) {
    if (typeof fn !== 'function') {
      throw new TypeError('promisify target must be a function')
    }

    const Ctor = this
    return function (...args) {
      const receiver = thisArg === undefined ? this : thisArg
      return new Ctor((resolve, reject) => {
        let callbackCalled = false
        const callback = (error, ...values) => {
          if (callbackCalled) return
          callbackCalled = true
          if (error != null) {
            reject(error)
          } else {
            resolve(values.length > 1 ? values : values[0])
          }
        }

        try {
          fn.apply(receiver, args.concat(callback))
        } catch (error) {
          reject(error)
        }
      })
    }
  }

  static deferred() {
    const deferred = {}
    deferred.promise = new this((resolve, reject) => {
      deferred.resolve = resolve
      deferred.reject = reject
    })
    return deferred
  }
}

if (typeof module !== 'undefined' && module.exports) {
  module.exports = MyPromise
}

实现中的几个关键点

1. 为什么要有两套“锁”

alreadyResolved 负责保证 executor 暴露的 resolve/reject 只有第一次调用有效;_state 负责保证内部最终状态只从 pending 转换一次。resolve 一个 pending thenable 时,外层已经 locked-in,但内部仍要等 thenable 的结果才能 fulfill/reject。

2. 为什么 resolvePromise 需要 called

恶意或错误 thenable 可能这样写:

const badThenable = {
  then(resolve, reject) {
    resolve('first')
    reject(new Error('ignored'))
    throw new Error('also ignored')
  },
}

协议要求第一次 resolve/reject 获胜;called 用来忽略后续调用和已完成后的异常。

3. 为什么必须清空 handler 队列

settle 后先取出队列并清空,再异步执行每个 handler,可以避免 Promise 长期持有已经执行过的闭包。真实引擎还会使用更复杂的内部记录和垃圾回收策略,这里只是教学版的内存保留优化。

4. 为什么 then 不是返回 this

如果返回 this,下面两个 handler 都观察同一个结果,无法表达“第二步等待第一步返回的 Promise”:

const p2 = p.then(() => getUserId())
const p3 = p2.then((id) => getBalance(id))

每一级新 Promise 都是一个桥梁,负责吸收前一级 handler 的返回值。

原文配图:Promises/A+ 测试输出截图

九、Promises/A+ 2.3 条款逐项对应

最终实现中的 resolvePromise 对应关系如下:

条款实现位置含义
2.3.1promise2 === x禁止链直接解析为自身
2.3.2let then = x.then 的 try/catch读取 then 时异常则 reject
2.3.3.1typeof then === 'function'判断 thenable
2.3.3.2读取异常分支then getter 抛错则 reject
2.3.3.3then.call(x, ...)以 thenable 为 this 调用
2.3.3.3.1/2called首次 resolve/reject 获胜
2.3.3.3.4递归 resolvePromiseresolve 的 y 继续解析
2.3.3.4then 非函数把 x 当普通值 fulfill

A+ 不要求构造器直接吸收 thenable,但当前实现为了贴近原生 Promise,在构造器 resolve 和静态 resolve 中也使用了同一套算法。

A+ 测试适配器

原文使用:

npx promises-aplus-tests mypromise.js

适配器需要暴露 MyPromise.deferred():

MyPromise.deferred = function () {
  const deferred = {}
  deferred.promise = new MyPromise((resolve, reject) => {
    deferred.resolve = resolve
    deferred.reject = reject
  })
  return deferred
}

promises-aplus-tests 主要测试 then 的异步调用、返回值、异常和 Resolution Procedure。通过该测试不能推出下面这些结论:

  • 构造器的 resolve 一定正确吸收任意 thenable。
  • all、race、resolve、reject 与原生静态方法完全一致。
  • 调度顺序与浏览器/Node 原生 Promise microtask 完全一致。
  • 子类、跨 realm、内置构造器和 finally 等扩展都兼容。

原文截图中的“通过测试”应作为历史记录保留,发布前应使用目标版本的测试套件重新运行,不能把截图当作当前运行证据。

原文配图:A+ 2.3.3 规范说明

十、all、race、resolve 和 reject

原文第七节把输入当作数组并直接调用 .then,这会漏掉普通值、Set、生成器和空 iterable。当前实现使用 Array.from 和 Ctor.resolve:

MyPromise.all([
  MyPromise.resolve(1),
  2,
  { then: (resolve) => resolve(3) },
]).then((values) => {
  console.log(values) // [1, 2, 3]
})

MyPromise.all([]).then((values) => {
  console.log(values) // []
})

MyPromise.race([
  new MyPromise((resolve) => setTimeout(() => resolve('slow'), 20)),
  'already fulfilled',
]).then(console.log) // already fulfilled

all

  • 接收 iterable,而不是只接收带 length 的数组。
  • 输入顺序决定结果数组顺序,不是完成顺序。
  • 普通值会通过 Ctor.resolve 变成 fulfilled Promise。
  • 空 iterable 立即 fulfilled 为 []。
  • 任意一项 rejected 时整体 rejected;其他已启动的操作不会自动取消。

race

  • 第一个 settled 的输入决定结果,可能是 fulfilled 也可能是 rejected。
  • 普通值可以立即获胜。
  • 空 iterable 永远 pending。
  • “最快”指 settle 时间,不是数组中位置,也不意味着底层任务会被取消。

当前原生 API 的扩展

现代 ECMAScript 还提供 finally、allSettled、any 等方法。本文只实现了 allSettled 的教学版本,没有实现完整的 any/AggregateError 语义。需要生产能力时使用原生 Promise,而不是不断扩展这个示例。

十一、promisify:把错误优先回调转换成 Promise

原文把 promisify 的英文名称拼错了,并使用 Bluebird 示例。Node.js 的约定通常是 (error, value):

const fs = require('node:fs')
const { promisify } = require('node:util')

const readFile = promisify(fs.readFile)

readFile('./package.json', 'utf8')
  .then((content) => console.log(content.length))
  .catch(console.error)

当前 Node.js 对文件系统已经提供了 Promise API,优先写:

const { readFile } = require('node:fs/promises')

async function readPackage() {
  const content = await readFile('./package.json', 'utf8')
  return content.length
}

教学版 MyPromise.promisify 需要注意:

  • 使用 error != null,不能用 truthiness 判断错误。
  • 需要保留 receiver 时传入 thisArg,或在方法对象上正确绑定。
  • Node 默认通常只返回一个成功值;多成功值 API 应明确返回数组或对象。
  • callback 被重复调用时,Promise 的一次性锁应让后续调用失效。
  • 已经返回 Promise 的函数不应重复 promisify;非标准 callback 签名需要自定义包装器。
const readFileWithMyPromise = MyPromise.promisify(fs.readFile, fs)

readFileWithMyPromise('./package.json', 'utf8')
  .then((content) => console.log(content.length))
  .catch(console.error)

十二、建议的回归测试

如果要继续完善这个教学实现,至少应覆盖:

const assert = require('node:assert/strict')

async function test() {
  const deferred = MyPromise.deferred()
  const events = []

  deferred.promise.then(
    (value) => events.push(`fulfilled:${value}`),
    (reason) => events.push(`rejected:${reason}`)
  )

  deferred.resolve(1)
  deferred.reject(2)
  await new Promise((resolve) => setTimeout(resolve, 0))

  assert.deepEqual(events, ['fulfilled:1'])

  const thenable = { then: (resolve) => resolve(42) }
  assert.equal(await MyPromise.resolve(thenable), 42)
  assert.deepEqual(await MyPromise.all([1, thenable]), [1, 42])
  assert.deepEqual(await MyPromise.all([]), [])

  const self = MyPromise.resolve()
  const chained = self.then(() => chained)
  await assert.rejects(chained, TypeError)
}

test().catch(console.error)

上面只是示意测试,不等于完整 test262 或 A+ 测试套件。尤其是跨 realm、代理、恶意 thenable、子类和特殊内置对象,需要专门的测试矩阵。

总结

从零实现 Promise 最重要的不是把代码写得很短,而是区分几个层次:

  1. 基础版本用于理解 executor 和回调注册,但不具备 Promise 语义。
  2. 状态和队列解决多个观察者、状态不可逆和异步 handler。
  3. 新 Promise 链和 resolvePromise 解决返回值传递、thenable 吸收和错误冒泡。
  4. alreadyResolved 解决构造器第一次 resolve/reject 的锁定;不能只在异步定时器中检查状态。
  5. A+ 主要覆盖 then 与 Resolution Procedure;静态方法、构造器和 Node promisify 需要另外按 ECMAScript/宿主 API 审计。
  6. 原生 Promise 已经经过引擎和标准库验证,生产代码不要用本文的 MyPromise 替换它。

参考资料:

原文参考:一起学习造轮子(一):从零开始写一个符合 Promises/A+ 规范的 promise。

807 DOCUMENTS · 12 COLLECTIONS
ARCHIVE SEARCH807 篇文章