关于前端:从头开始实现-Promise

4次阅读

共计 11164 个字符,预计需要花费 28 分钟才能阅读完成。

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


实现 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) — 通过 valuePENDINGFULFILLEDvalue 当初是 promise 的属性。
  • reject(reason) — 通过 reasonPENDINGREJECTEDreason 当初是 promise 的属性。

最后的实现很简略:

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

class APromise {constructor(executor) {
    // 初始化状态
    this.state = PENDING
    // 胜利的 value 或回绝的 reason 在外部映射为 value,最后 promise 没有值

    // 调用立刻执行程序
    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(译为 处理程序)。

让咱们将 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.catch()

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

实现 Promise.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 并获得其 result/error,第一个 settled promise 之后,所有其余的 result/error 都会被疏忽。

class APromise {
  // ...
  static race(arr) {
    const _Promise = this
    if (!Array.isArray(arr)) {return _Promise.reject(new TypeError('race() only accepts an array'))
    }
    return new _Promise((resolve, reject) => {arr.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) // 100
APromise.race([err(50), err(60)]).catch(console.error) // 50

实现 Promise.all()

  • 如果传入的可迭代对象内的 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+ 残缺示例。

  • JavaScript Promise 迷你书(中文版)
  • Promises/A+
  • Promises/A 与 Promises/A+ 的区别
  • Conformant Implementations
  • 通过从头开始构建 Promise 来学习 JavaScript Promise
  • implementing
  • Optimization killers
  • es6-promise
  • 性能晋升优化技巧
  • 对于异步堆栈跟踪
正文完
 0