技术知识文章集合TECHNICAL ARCHIVE · 457 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 如何解析。
  • 错误如何穿过链条,以及为什么 catchthen(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 吸收
allraceresolvereject不属于 A+ 核心静态方法接收 iterable 并处理普通值
catchfinallyallSettledany不属于 A+ 核心由 ECMAScript 提供

因此,“通过 promises-aplus-tests”只能说明测试覆盖范围内的 then 适配符合 A+,不能证明构造器、thenable、allracepromisify 与原生 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) 创建 p2f1 的结果决定 p2
  2. p2.then(f2) 创建 p3f2 的结果决定 p3
  3. p3.then(f3) 创建 p4f3 的结果决定 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 是函数,以 xthis 调用它。
  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.thentry/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。
  • allraceresolvereject 与原生静态方法完全一致。
  • 调度顺序与浏览器/Node 原生 Promise microtask 完全一致。
  • 子类、跨 realm、内置构造器和 finally 等扩展都兼容。

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

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

十、allraceresolvereject

原文第七节把输入当作数组并直接调用 .then,这会漏掉普通值、Set、生成器和空 iterable。当前实现使用 Array.fromCtor.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 还提供 finallyallSettledany 等方法。本文只实现了 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

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS