从头开始实现 Promise

本文已整理到 Github,地址 👉 blog


本文的目标是编写一个与 then/promise 类似的符合 Promise/A+ 的实现。

以下前半部分译自 Implementing promises from scratch,也是本文的重点。你可以查看原文,它还使用 TDD 方式,编写一些测试用例,帮助你理解。下半部分是 Promise 各个方法的实现。

Promise 状态

Promise 是必须处于以下状态之一的对象/函数:PENDINGFULFILLEDREJECTED,最初 promise 处于 PENDING 状态。

Promise 可以从 PENDING 状态转换为带 value 值的 FULFILLED 状态或带 reasonREJECTED 状态。

为了进行状态转换,promise 构造函数接收到一个名为 executor 的函数,executor 会立即被调用,调用时使用两个函数 fulfillreject 来执行状态转换:

  • fulfill(value) — 从 PENDING 状态转化到 FULFILLED 带有 valuevalue 现在是 promise 的一个属性。
  • reject(reason) — 从 PENDING 状态转化到 REJECTED 带有 reasonreason 现在是 promise 的一个属性。

最初的实现很简单:

// 可能的状态
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class APromise {
  constructor(executor) {
    // 初始化状态
    this.state = PENDING
    // 成功的 value 或拒绝的 reason 在内部映射为 value,最初 promise 没有 value

    // 调用立即执行程序
    doResolve(this, executor)
  }
}

// 带 value 的 fulfill
function fulfill(promise, value) {
  promise.state = FULFILLED
  promise.value = value
}

// 带 reason 的 reject
function reject(promise, reason) {
  promise.state = REJECTED
  promise.value = reason
}

// 创建作为 executor 参数的 fulfill/reject  函数
function doResolve(promise, executor) {
  function wrapFulfill(value) {
    fulfill(promise, value)
  }

  function wrapReject(reason) {
    reject(promise, reason)
  }

  executor(wrapFulfill, wrapReject)
}

观察状态变化

为了观察 promise 状态的变化(以及成功的值或拒绝的原因),我们使用 then 方法,该方法接收两个参数,一个 onFulfilled 函数和一个 onRejected 函数,调用这些函数的规则如下:

  • 当 promise 处于 FULFILLED 状态时,onFulfilled 函数将被调用,并带有 promise 履行的 value,例如 onFulfilled(value)
  • 当 promise 处于 REJECTED 状态时,onRejected 函数将被调用,并带有 promise 被拒绝的 reason,例如 onRejected(reason)

我们将这些函数称为 promise handlers(译为处理程序,以下关于 handler 将不翻译)。

让我们将 then 函数添加到类原型中,注意它会根据 Promise 的状态调用 onFulfilledonRejected 函数。

class APromise {
  // ...
  then(onFulfilled, onRejected) {
    handleResolved(this, onFulfilled, onRejected)
  }
  // ...
}

function handleResolved(promise, onFulfilled, onRejected) {
  const cb = promise.state === FULFILLED ? onFulfilled : onRejected
  cb(promise.value)
}

单向转换

一旦转换到其中一个 FULFILLEDREJECTED 状态,promise 不得转换到任何其他状态。

在我们当前的实现中,调用 executor 的函数应该确保只调用一次 fulfillreject,后续调用应该被忽略

function doResolve(promise, executor) {
  let called = false

  function wrapFulfill(value) {
    if (called) return
    called = true
    fulfill(promise, value)
  }

  function wrapReject(reason) {
    if (called) return
    called = true
    reject(promise, reason)
  }

  executor(wrapFulfill, wrapReject)
}

处理 executor 错误

如果执行 executor 失败,promise 应转换到 REJECTED 状态,并说明失败原因

function doResolve(promise, executor) {
  // ...
  try {
    executor(wrapFulfill, wrapReject)
  } catch (err) {
    wrapReject(err)
  }
}

异步 executor

如果解析器的 fulfill/reject 是异步执行,则我们的 .then 方法将失败,因为它的 handlers 将立即执行。

让我们向 Promise 添加一个队列,它的目的是存储一旦 Promise 状态从 PENDING 更改为其他状态时将调用的 handlers,同时我们的 .then 方法应该检查 Promise 状态,以决定是立即调用 handler 还是存储 handler,让我们将此逻辑移动到新的辅助函数 handle

class APromise {
  constructor(executor) {
    this.state = PENDING
    // 存储 .then handler 队列
    this.queue = []
    doResolve(this, executor)
  }

  then(onFulfilled, onRejected) {
    handle(this, { onFulfilled, onRejected })
  }
}

// 检查 promise 的状态:
// - 如果 promise 为 PENDING,将其推入 queue 以供以后使用
// - 如果 promise 还不是 PENDING,则调用 handler
function handle(promise, handler) {
  if (promise.state === PENDING) {
    // 如果为 PENDING,推入 queue
    promise.queue.push(handler)
  } else {
    // 立即执行
    handleResolved(promise, handler)
  }
}

function handleResolved(promise, handler) {
  const cb =
    promise.state === FULFILLED ? handler.onFulfilled : handler.onRejected
  cb(promise.value)
}

此外,应该更新 fulfillreject 方法,以便在调用时调用 Promise 中存储的所有 handlers,这将在更新状态和值后调用的新函数 finale 中实现。

function fulfill(promise, value)
  // ...
  finale(promise)
}

function reject(promise, reason) {
  // ...
  finale(promise)
}

// 调用 promise 中存储的所有 handlers
function finale(promise) {
  const length = promise.queue.length
  for (let i = 0; i < length; i += 1) {
    handle(promise, promise.queue[i])
  }
}

链式的 Promise

我们的 .then 方法应该返回一个新的 Promise。

实现也很简单,但是我们将看到新的 Promise 以不同于使用 executor 的方式转换到不同的状态,新的 Promise 使用 handlers 进行转换,如下所示:

  • 如果 onFulfilledonRejected 函数被调用
    • 如果执行时没有错误,Promise 将转换为 FULFILLED 状态,返回值作为 value
    • 如果执行时出现错误,Promise 将转换到 REJECTED 状态,并将错误作为 reason

让我们做一个 .then 方法首先返回 Promise:

class APromise {
  // ...
  then(onFulfilled, onRejected) {
    // 空的 executor
    const promise = new APromise(() => {})
    handle(this, { onFulfilled, onRejected })
    return promise
  }
}

对于实现,我们首先必须将新的 Promise 也存储在 handler 队列中,这样,如果观察到的 Promise 被解析,那么队列中的元素就知道需要解析哪个 Promise。

class APromise {
  // ...
  then(onFulfilled, onRejected) {
    const promise = new APromise(() => {})
    // 同时保存 promise
    handle(this, { promise, onFulfilled, onRejected })
    return promise
  }
}

function handleResolved(promise, handler) {
  const cb =
    promise.state === FULFILLED ? handler.onFulfilled : handler.onRejected
  // 执行 handler 并根据规则进行转换
  try {
    const value = cb(promise.value)
    fulfill(handler.promise, value)
  } catch (err) {
    reject(handler.promise, err)
  }
}

异步 handlers

接下来,让我们考虑 handler 返回 Promise 的情况,在这种情况下,作为 handler 一部分的 Promise(不是返回的 Promise)应该采用返回 Promise 的状态履行值或拒绝原因。

让我们设想以下场景:

const executor = fulfill => setTimeout(fulfill, 0, 'p')
const p = new APromise(executor)

const qOnFulfilled = value =>
  new APromise(fulfill => fulfill(value + 'q'))
const q = p.then(qOnFulfilled)

const rOnFulfilled = value => (
  // 值应为 pq
)
const r = q.then(rOnFulfilled)

在我们当前的实现中,元组 { q, qOnFulfilled } 存储在 p 的 handlers 中,并且我们确信在 q 存储元组 { r, rOnFulfilled } 之前,qOnFulfilled 被调用,我们可以利用这一事实,并检测 handler 何时返回一个 Promise,在返回的 Promise 中存储观察者,例如在 qOnFulfilled 返回的 Promise 上存储 { r, onFulfilled }

请注意,我们使用的是 while,因为嵌套的 Promise 本身可能有另一个 Promise 作为解析值。

function handle(promise, handler) {
  // 取最深处的 promise 的状态
  while (promise.value instanceof APromise) {
    promise = promise.value
  }

  // ...
}

其他情况

无效的 handlers

如果原本应该是函数的 handler 不是函数,那么我们的实现就会失败:

const p = new APromise((fulfill) => fulfill('p'))
const qOnFulfilled = null
const q = p.then(qOnFulfilled)

在这种情况下,q 应该立即用 p 的值进行解析

function handleResolved(promise, handler) {
  const cb =
    promise.state === FULFILLED ? handler.onFulfilled : handler.onRejected
  // 如果 handler 不是函数,则立即解析
  if (typeof cb !== 'function') {
    if (promise.state === FULFILLED) {
      fulfill(handler.promise, promise.value)
    } else {
      reject(handler.promise, promise.value)
    }
    return
  }
  // ...
}

在事件循环之后执行 handlers

要求 2.2.4,正如 3.1 中指出的,handlers 被一个新的堆栈调用,此外,即使 executor/handlers 是同步的,也可以确保将来调用观察者,从而使 Promise 解析保持一致。

我们可以使用任何允许我们在事件循环之后调用函数的函数,这包括 setTimeoutsetImmediaterequestAnimationFrame

function handleResolved(promise, handler) {
  setImmediate(() => {
    // ...
  })
}

以已解决的 Promise 为理由拒绝

要求 2.2.7.2,只有当 promise 不处于 REJECTED 状态时才采用嵌套 promise 的状态。

function handle(promise, handler) {
  // 以返回的 promise 的状态为例
  while (promise.state !== REJECTED && promise.value instanceof APromise) {}
  // ...
}

Promise 本身无法解决

要求 2.3.1,在 fulfill 方法上,让我们检查履行值是否等于 Promise 本身,如果是这样,则抛出一个 TypeError

function fulfill(promise, value) {
  if (value === promise) {
    return reject(
      promise,
      new TypeError('A promise cannot be resolved with itself.')
    )
  }

  // ...
}

Thenable

根据 2.3.3.3 相关要求,handler 的返回值可能是一个 thenable,一个 object/function,它具有一个可访问的 then 属性,这是一个函数,then 函数就像一个 executor,它接收一个 fulfillreject 回调,应该用来转换 thenable 的状态。

让我们修改 fulfill 方法并添加对 thenable 的检查,注意访问属性并不总是安全的操作(例如,属性可能使用 getter),这就是为什么我们应该将它包装在 try/catch 中。

另外,thenable 的 then 应该被调用为 this

function fulfill(promise, value) {
  if (value === promise) {
    return reject(
      promise,
      new TypeError('A promise cannot be resolved with itself.')
    )
  }

  if (value && (typeof value === 'object' || typeof value === 'function')) {
    let then
    try {
      then = value.then
    } catch (err) {
      return reject(promise, err)
    }

    // promise
    if (then === promise.then && promise instanceof APromise) {
      promise.state = FULFILLED
      promise.value = value
      return finale(promise)
    }

    // thenable
    if (typeof then === 'function') {
      return doResolve(promise, then.bind(value))
    }
  }

  // primitive
  promise.state = FULFILLED
  promise.value = value
  finale(promise)
}

实现 Promise.prototype.catch()

Promise.prototype.catch 用于处理拒绝的情况,是特殊的 .then 方法,调用 catch 之后,可以继续使用 .then

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

实现 Promise.prototype.finally()

Promise.prototype.finally() 返回一个 Promise。在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式。

将返回 promise 的结果在 then 一次,无论返回的是 fulfilled 还是 rejected,都执行给定的回调函数。

class APromise {
  finally(onFinally) {
    return this.then(
      /* onFulfilled */
      (res) => Promise.resolve(onFinally().call(this)).then(() => res),
      /* onRejected */
      (err) =>
        Promise.resolve(onFinally().call(this)).then(() => {
          throw err
        })
    )
  }
}

示例:

new APromise((resolve, reject) => {
  resolve('ok')
})
  .then((res) => {
    console.log(res) // ok
  })
  .finally(() => {
    console.log('finally') // finally
  })

实现 Promise.resolve()

内部返回一个 promise,调用 resolve 方法,将 value 参数传入

class APromise {
  // ...
  static resolve(value) {
    return new APromise((resolve) => resolve(value))
  }
}

实现 Promise.reject()

内部返回了一个 promise,调用 reject 方法,将 reason 参数传入

class APromise {
  // ...
  static reject(reason) {
    return A((resolve, reject) => reject(reason))
  }
}

实现 Promise.race()

Promise.racePromise.all 类似,但只等待第一个 settled 的 promise 并取得其 value/reason,第一个 settled promise 之后,所有其他的 value/reason 都会被忽略。

如果传的参数数组是空,则返回的 promise 将永远等待。

class APromise {
  // ...
  static race(promises) {
    const _Promise = this
    if (!Array.isArray(promises)) {
      return _Promise.reject(new TypeError('race() only accepts an array'))
    }
    return new _Promise((resolve, reject) => {
      promises.forEach((p) => {
        _Promise.resolve(p).then(resolve, reject)
      })
    })
  }
}

示例:

const sleep = (sm) =>
  new APromise((resolve) => setTimeout(() => resolve(sm), sm))
const err = (ms) => sleep(ms).then(() => APromise.reject(ms))

APromise.race([1, 2, 3]).then(console.log) // 1
APromise.race([sleep(300), sleep(100), sleep(200)]).then(console.log) // 100
APromise.race([sleep(3000), err(100), sleep(2000)]).catch(console.error) // Error: 100
APromise.race([err(50), err(60)]).catch(console.error) // 50

实现 Promise.all()

  • 如果传入的可迭代对象为空,那么此 promise 对象回调完成,直接 resolve
  • 如果传入的可迭代对象内的 promise 全部成功,那么就返回 resolve 成功的数组
  • 一旦有一个 promise 执行失败,Promise.all 直接返回错误的那个 reject
class APromise {
  // ...
  static all(promises) {
    let remaining = promises.length
    // 判断是否为空
    if (remaining === 0) return APromise.resolve([])

    return new APromise((resolve, reject) => {
      promises.reduce((acc, promise, i) => {
        APromise.resolve(promise).then(
          (res) => {
            acc[i] = res
            --remaining || resolve(acc)
          },
          (err) => {
            reject(err)
          }
        )
        return acc
      }, [])
    })
  }
}

测试:

const sleep = (sm) =>
  new APromise((resolve) => setTimeout(() => resolve(sm), sm))
const err = (ms) => sleep(ms).then(() => APromise.reject(ms))

APromise.all([1, 2, 3]).then(console.log) // [1, 2, 3]
APromise.all([sleep(300), sleep(100), sleep(200)]).then(console.log)
APromise.all([sleep(3000), err(100), sleep(2000)]).catch(console.error) // 100
APromise.all([err(50), err(60)]).catch(console.error) // 50

实现 promise.any()

Promise.any() 接收一个 Promise 可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promiseAggregateError 类型的实例。

Promise.any 的行为跟 Promise.all 刚好相反:

  • 只要有一个成功,那么就立即 resolve 出去
  • 如果全部失败,那么就将所有 reject 结果收集起来并返回 AggregateError
class APromise {
  // ...
  static any(promises) {
    return A((resolve, reject) => {
      if (promises.length === 0)
        return reject(new AggregateError('All promises were rejected'))
      promises.reduce((acc, cur) => {
        Promise.resolve(cur).then(
          (data) => {
            resolve(data)
          },
          (err) => {
            acc.push(err)
            if (acc.length === promises.length)
              reject(new AggregateError('All promises were rejected'))
          }
        )
        return acc
      }, [])
    })
  }
}

示例:

const sleep = (sm) =>
  new APromise((resolve) => setTimeout(() => resolve(sm), sm))
const err = (ms) => sleep(ms).then(() => APromise.reject(ms))

APromise.any([1, 2, 3]).then((o) => console.log(o)) // 1
APromise.any([sleep(3000), err(100), sleep(2000)]).then(console.info) // 2000
APromise.any([err(50), err(60)]).catch(console.log) // AggregateError

实现 Promise.allSettled()

Promise.allSettled() 方法返回一个在所有给定的 promise 都已经 fulfilled 或 rejected 后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。

class APromise {
  // ...
  static allSettled(values) {
    let promises = [].slice.call(values)

    return new APromise((resolve, reject) => {
      let result = [],
        count = 0

      promises.forEach((promise) => {
        APromise.resolve(promise)
          .then((value) => {
            result.push({ status: 'fulfilled', value })
          })
          .catch((err) => {
            result.push({ status: 'rejected', value: err })
          })
          .finally(() => {
            if (++count === promises.length) {
              resolve(result)
            }
          })
      })
    })
  }
}

示例:

const sleep = (sm) =>
  new APromise((resolve) => setTimeout(() => resolve(sm), sm))
const err = (ms) => sleep(ms).then(() => APromise.reject(ms))

APromise.allSettled([1, 2, 3]).then(console.log)
APromise.allSettled([sleep(300), sleep(100), sleep(200)]).then(console.log)
APromise.allSettled([sleep(3000), err(100), sleep(2000)]).catch(console.error)
APromise.allSettled([err(50), err(60)]).then(console.log)

更多资料

以上 Promise/A+ 完整示例

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容