Skip to content

Latest commit

 

History

History
659 lines (521 loc) · 19.9 KB

实现 Promise A+.md

File metadata and controls

659 lines (521 loc) · 19.9 KB

实现 Promise/A+

本文的目标是编写一个与 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+ 完整示例