coverPiccoverPic

实现 Promise A+ 规范的 Promise

前言

之前找工作的时候凭感觉做了一个实现 Promise A+ 规范的 Promise的练习,最近在准备新的工作机会,又看到了这个面试题。

我感觉之前的实现有很大优化空间。之前用前次调用结果作为标记来实现 Promise 多次 resolve 和 reject 触发的正确逻辑,感觉有点太麻烦了,通过和 AI 的深入交流,发现完全可以用简单的布尔值标记做到。

这篇博客是前文的优化版,包含了前文已有的 Promise A+ 的一些介绍,权当是复习吧…

前文看这里:https://deer.shika-blog.xyz/web/article/108 (各种错误有点多)

简介 Promise A+ 规范

变量和术语

Promise 表示异步操作的最终结果。

  1. Promise 具有 3 种状态:pending(等待中)、fulfilled(成功执行)、rejected(失败拒绝),初始状态为 pending,切换为 fulfilled 或者 rejected 后就不能再转换。处于非 pending 状态时称为 settled。
ts
  1. const testPromise = new Promise((resolve, reject) => {
  2. // DO SOMETHING
  3. })

像这样子,传入的函数我们称为executorresolvereject会触发 Promise 的状态改变以及数据更新。

value表示成功执行(fulfilled 状态)的 Promise 的结果,reason表示失败拒绝(rejected 状态)的 Promise 的原因,它们可以取 JS 中任何合法的值。

Promise A+ 规范的 Promise 上的方法只有简单的thencatchfinally之类的方法并不包含。

graph
    A[创建Promise] --> B["执行executor(resolve, reject)"]
    
    
    F{"executor执行结果?"}
    C -->|settled|G[忽略重复调用]
    C -->|not settled|E
    B --> F
    
    F -->|"调用resolve(value)"| I{"Promise settled?"}
    I -->|settled|G
    I -->|not settled| D["状态: pending → fulfilled
存储value"] F -->|抛出异常| C F -->|"调用reject(reason)"| C{"Promise settled?"} E["状态: pending → rejected
存储reason"] F -->|"当前未执行resolve和reject,没有抛出异常"| H[pending] style A fill:#e1f5ff style B fill:#e1f5ff

then 方法

  1. then方法具有onFulfilledonRejected两个入参,返回一个 Promise(链式调用)。

举个栗子:

ts
  1. const temp = testPromise.then(function onFulfilled (value) {
  2. // DO SOMETHING
  3. }, function onRejected (reason) {
  4. // DO SOMETHING
  5. })
  6. console.log(temp instanceof Promise) // true
  7. console.log(temp === testPromise) // false
  • Promise 从 pending 状态切换到 fulfilled 或者 rejected 时,执行此前then传入的onFulfilledonRejected。fulfilled 状态的 Promise 会执行then传入的onFulfilled,rejected 状态的 Promise 会执行then传入的onRejected

  • 执行onFulfilledonRejected的结果会被传入新的 Promise tempresolve方法中,如果发生了错误则传入reject中,改变的状态和数据。

  • onFulfilled或者onRejected不是函数时,返回的 Promise 与原 Promise 具有相同的状态和数据(传值穿透)。

用一个流程图总结一下:

graph 
    
    F["调用promise.then(onFulfilled, onRejected)"] --> G{"当前状态?"}
    
    G -->|pending| H["注册回调到队列
等待状态改变"] G -->|fulfilled| I["异步执行onFulfilled(value)"] G -->|rejected| J["异步执行onRejected(reason)"] I --> K{"onFulfilled返回值?"} J --> L{"onRejected返回值?"} K -->|正常返回| M["Promise Resolution Procedure"] L -->|正常返回| M K -->|抛出异常| O["调用新Promise的reject
状态: rejected"] L -->|抛出异常| O H -->|状态变为fulfilled| I H -->|状态变为rejected| J O --> Z[返回新Promise] style F fill:#fff2e1 style G fill:#f0e1ff style M fill:#e8f5e9 style H fill:#fff9c4

Promise Resolution Procedure

  1. resolve被触发时发生什么事了?此时 Promise 的状态仍未真正变化,会进入一段处理程序,规范称之为 Promise Resolution Procedure,主要逻辑是如果传入的是非 thenable 对象或者基本类型则直接修改 Promise 的状态和数据,是 thenable 就执行下面 thenable 相关逻辑。
  • 此外,不支持我返回我自己,onFulfilled或者onRejected返回该then返回的 Promise 时,抛出TypeError错误,例如:
ts
  1. const temp = testPromise.then(function onFulfilled (value) {
  2. return temp
  3. })

处理 thenable 对象

  1. thenable 的对象是具有then方法的对象或者函数。then方法接受两个回调函数onResolvePromiseonRejectPromise,类似于这里的 Promise 的then。thenable 实际上包括实现了 Promise A+ 规范的 Promise,例如 ES6 原生的 Promise。举个 thenable 对象的栗子:
ts
  1. const thenable = {
  2. then: function (onResolvePromise, onRejectPromise) {
  3. onResolvePromise('miao~~')
  4. }
  5. }
  • 如果触发了onFulfilled,返回了一个 thenable。如果是该 Promise 的实例,不是当前 Promise,则传入当前 Promise 的resolvereject,调用then方法。

  • 兼容其他 thenable:调用then方法,传入当前 Promise 的resolvereject,像该 Promise 实例一样解析。

  • 允许其他 thenable 对象乱写,这里需要处理 thenable 对象重复触发onResolvePromise或/和onRejectPromise的情况,这两个回调函数最多只能改变 1 次 Promise 的状态。

  1. 其他详细见 Promise A+ 规范。

这里再用个流程图总结一下

graph 
    M["Promise Resolution Procedure"]
    
    M --> P{返回值是thenable?}
    P -->|是| Q{是否返回自身?}
    Q -->|是| R[抛出TypeError]
    Q -->|否| S["调用thenable.then(resolvePromise, rejectPromise)"]
    
    P -->|否| N
    
    S --> T{thenable行为?}
    T -->|"调用resolvePromise(x)"| U{Promise settled?}
    T -->|"调用rejectPromise(reason)"| V{Promise settled?}
    T -->|抛出异常| O[调用新Promise的reject
状态: rejected] U -->|not settled| W["状态: fulfilled
value = x"] V -->|not settled| X["状态: rejected
reason = reason"] U -->|settled| Y[忽略重复调用] V -->|settled| Y N[调用新Promise的resolve
状态: fulfilled] --> Z[返回新Promise] O --> Z W --> Z X --> Z R --> AA["返回rejected Promise
reason = TypeError"] style M fill:#e8f5e9

前期准备

先定义好类型和一个发起微任务的辅助函数。

ts
  1. enum PromiseState {
  2. fulfilled = 'fulfilled',
  3. pending = 'pending',
  4. rejected = 'rejected'
  5. }
  6. type Executor<T> = (
  7. resolve: (value: T | PromiseLike<T>) => void,
  8. reject: (reason?: any) => void
  9. ) => void
  10. const scheduleMicrotask = (callback: () => void) => {
  11. if (typeof queueMicrotask === 'function') {
  12. queueMicrotask(callback)
  13. } else if (typeof process !== 'undefined' && process.nextTick) {
  14. process.nextTick(callback)
  15. } else {
  16. Promise.resolve().then(callback)
  17. }
  18. }

简单地写一个 Promise

ts
  1. class ShikaPromise<T = any> {
  2. private state: PromiseState = PromiseState.pending
  3. private value: T | undefined
  4. private reason: any
  5. constructor(executor: Executor<T>) {
  6. try {
  7. executor(
  8. (value) => this.resolve(value),
  9. (reason) => this.reject(reason)
  10. )
  11. } catch (error) {
  12. this.reject(error)
  13. }
  14. }
  15. private resolve(value: T): void {
  16. // 不支持 resolve 自己
  17. if (value === this) {
  18. this.reject(new TypeError('Cannot resolve promise with itself'))
  19. return
  20. }
  21. scheduleMicrotask(() => {
  22. this.state = PromiseState.fulfilled
  23. this.value = value
  24. })
  25. }
  26. private reject(reason: any): void {
  27. scheduleMicrotask(() => {
  28. this.state = PromiseState.rejected
  29. this.reason = reason
  30. })
  31. }
  32. then<TResult1 = T, TResult2 = never>(
  33. onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,
  34. onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined
  35. ) {
  36. // TODO
  37. }
  38. }

下面就来写then方法实现异步的链式调用。

then 方法

then返回一个 Promise,虽然 Promise A+ 规范没有说明需要返回的 Promise 不能和原有的是同一个,但是考虑到后续链式调用也会涉及到 Promise 状态的改变,所以这里就返回一个新的 Promise。

fulfilled 和 rejected 状态

假设const promise2 = promise1.then(onFulfilled, onRejected),调用promise1.then时创建一个新的 Promise promise2返回出去。用过 ES6 的Promise很好理解,如果原有promise1是 fulfilled 的,则在新的promise2executor中的resolve传入onFulfilled的结果,如果promise1处于失败状态,rejected 了,则在promise2resolve中传入onRejected的结果。

举个栗子:

ts
  1. const promiseTmp1 = Promise.resolve('ok').then(value => value, reason => reason)
  2. // 此时 promiseTmp1.value 是 'ok'
  3. const promiseTmp2 = Promise.resolve('error').then(value => value, reason => reason)
  4. // 此时 promiseTmp2.value 是 'error'

下面编写 fulfilled 和 rejected 状态的处理逻辑。

ts
  1. // ...
  2. class ShikaPromise {
  3. // ...
  4. then<TResult1 = T, TResult2 = never>(
  5. onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,
  6. onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined
  7. ): ShikaPromise<TResult1 | TResult2> {
  8. return new ShikaPromise<TResult1 | TResult2>((resolve, reject) => {
  9. const handleCallback = (isFulfilled: boolean) => {
  10. scheduleMicrotask(() => {
  11. const callback = isFulfilled ? onFulfilled : onRejected
  12. const data = isFulfilled ? this.value : this.reason
  13. // 传值穿透
  14. if (typeof callback !== 'function') {
  15. if (isFulfilled) {
  16. resolve(data as TResult1)
  17. } else {
  18. reject(data)
  19. }
  20. return
  21. }
  22. try {
  23. const result = callback(data)
  24. resolve(result)
  25. } catch (error) {
  26. reject(error)
  27. }
  28. })
  29. }
  30. switch (this.state) {
  31. case PromiseState.fulfilled:
  32. handleCallback(true)
  33. break
  34. case PromiseState.rejected:
  35. handleCallback(false)
  36. break
  37. default:
  38. // TODO
  39. }
  40. })
  41. }
  42. }

pending 状态

promise1在等待的时候,可以在promise1上新建两个属性fulfilledHandlersrejectedHandlers缓存给promise2触发resolvereject的回调函数。promise2处于 pending 状态,promise1切换状态后触发这些回调函数,用来改变promise2的状态。

ts
  1. // ...
  2. class ShikaPromise {
  3. // ...
  4. // 记录等待 fulfilled 或者 rejected 后执行的回调函数
  5. private fulfilledHandlers: Array<() => void> = []
  6. private rejectedHandlers: Array<() => void> = []
  7. // ...
  8. private resolve(value: T): void {
  9. scheduleMicrotask(() => {
  10. this.state = PromiseState.fulfilled
  11. this.value = value
  12. const handlers = this.fulfilledHandlers.splice(0)
  13. handlers.forEach((h) => h())
  14. })
  15. }
  16. private reject(reason: any): void {
  17. scheduleMicrotask(() => {
  18. this.state = PromiseState.rejected
  19. this.reason = reason
  20. const handlers = this.rejectedHandlers.splice(0)
  21. handlers.forEach((h) => h())
  22. })
  23. }
  24. // ...
  25. then (onFulfilled?: ThenCallback, onRejected?: ThenCallback) {
  26. return new ShikaPromise<TResult1 | TResult2>((resolve, reject) => {
  27. // ...
  28. switch (this.state) {
  29. // ...
  30. default:
  31. this.fulfilledHandlers.push(() => handleCallback(true))
  32. this.rejectedHandlers.push(() => handleCallback(false))
  33. }
  34. })
  35. }
  36. }

防止多次触发

我们通过添加标记isResolved记录是否已经触发resolve。当重复触发resolvereject时,遇到isResolvedtrue就返回。

ts
  1. // ...
  2. class ShikaPromise<T = any> {
  3. // ...
  4. private isResolved = false
  5. // ...
  6. private resolve(value: T | PromiseLike<T>): void {
  7. if (this.isResolved) return
  8. if (value === this) {
  9. this.reject(new TypeError('Cannot resolve promise with itself'))
  10. return
  11. }
  12. // TODO: thenable 处理
  13. this.fulfill(value as T)
  14. }
  15. private fulfill(value: T): void {
  16. if (this.isResolved) return
  17. this.isResolved = true
  18. scheduleMicrotask(() => {
  19. this.state = PromiseState.fulfilled
  20. this.value = value
  21. const handlers = this.fulfilledHandlers.splice(0)
  22. handlers.forEach((h) => h())
  23. })
  24. }
  25. private reject(reason: any): void {
  26. if (this.isResolved) return
  27. this.isResolved = true
  28. // ...
  29. }
  30. // ...
  31. }

解析 thenable 对象

如果遇到 thenable 对象,等待其进入 fulfilled 或者 rejected 状态,同样的,thenable 对象也需要防止重复进入 fulfilled 和 rejected 状态。

ts
  1. class ShikaPromise<T = any> {
  2. // ...
  3. private resolve(value: T | PromiseLike<T>): void {
  4. // ...
  5. const thenable = this.getThenable(value)
  6. if (thenable) {
  7. this.resolveThenable(thenable)
  8. } else {
  9. this.fulfill(value as T)
  10. }
  11. }
  12. private getThenable(value: any): { then: Function; target: any } | null {
  13. if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
  14. try {
  15. // 在规范中有 Let then be x.then 的描述,测试用例中 value.then 只能被取一次
  16. const then = value.then
  17. if (typeof then === 'function') {
  18. return { then, target: value }
  19. }
  20. } catch (error) {
  21. this.reject(error)
  22. }
  23. }
  24. return null
  25. }
  26. private resolveThenable(thenable: { then: Function; target: any }): void {
  27. let called = false
  28. try {
  29. thenable.then.call(
  30. thenable.target,
  31. (value: any) => {
  32. if (called) return
  33. called = true
  34. this.resolvevaluey)
  35. },
  36. (reason: any) => {
  37. if (called) return
  38. called = true
  39. this.reject(reason)
  40. }
  41. )
  42. } catch (error) {
  43. if (!called) this.reject(error)
  44. }
  45. }
  46. }

其他方法

JS 的 Promise

下面就来实现一下 JS 的 Promse 的catchfinallycatch就是then方法只提供第二个参数。finally方法回调函数不接收任何参数,返回一个状态和数据与原来相同的 Promise。

ts
  1. class ShikaPromise {
  2. catch<TResult = never>(
  3. onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined
  4. ): ShikaPromise<T | TResult> {
  5. return this.then(null, onRejected)
  6. }
  7. finally(onFinally?: (() => void) | null | undefined): ShikaPromise<T> {
  8. return this.then(
  9. (value) => {
  10. onFinally?.()
  11. return value
  12. },
  13. (reason) => {
  14. onFinally?.()
  15. throw reason
  16. }
  17. )
  18. }
  19. }

还有Promise.resolvePromise.reject两个静态方法:

ts
  1. class ShikaPromise {
  2. static resolve<T>(value: T | PromiseLike<T>): ShikaPromise<T> {
  3. return value instanceof ShikaPromise ? value : new ShikaPromise((resolve) => resolve(value))
  4. }
  5. static reject<T = never>(reason?: any): ShikaPromise<T> {
  6. return new ShikaPromise((_, reject) => reject(reason))
  7. }
  8. }

如果 Promise 可以停止

如果想要 Promise 后面的thencatchfinally)都不会触发,这里只需要返回一个 pending 状态的 Promise。这里实现一个时链式调用停止的cancel方法和返回 pending 的 Promise 的wait方法:

ts
  1. class ShikaPromise {
  2. static wait(): ShikaPromise<never> {
  3. return new ShikaPromise(() => {})
  4. }
  5. cancel(): ShikaPromise<never> {
  6. return new ShikaPromise(() => {})
  7. }
  8. }

Promise A+ 测试

下载 promises-aplus-tests 包:

cmd
  1. npm i promises-aplus-tests

要求 Promise 所在文件采用 commonjs 方式导出。还需要在 Promise 上实现静态方法:

ts
  1. class ShikaPromise {
  2. static deferred<T>() {
  3. let resolve!: (value: T | PromiseLike<T>) => void
  4. let reject!: (reason?: any) => void
  5. const promise = new ShikaPromise<T>((res, rej) => {
  6. resolve = res
  7. reject = rej
  8. })
  9. return { promise, resolve, reject }
  10. }
  11. }

promises-aplus-tests Promise 的所在文件即可运行,如果你在用 TS,文件为编译后的文件,例如:

cmd
  1. promises-aplus-tests dist/文件名.js

Promise A+ 的测试用例覆盖面非常全,调试时烦死了x,通过了所有 817 条用例,就说明你的 Promise 实现了 Promise A+ 标准了。

我把 TS 编译和运行测试用例在 package.json 组装成一条命令:

json
  1. {
  2. // ...
  3. "scripts": {
  4. // ...
  5. "test": "tsc && promises-aplus-tests dist/文件名.js",
  6. },
  7. // ...
  8. }

这里 tsc 会默认编译 tsconfig.json 设置的根目录(这里是 ./src),然后放到输出目录中(这里是 ./dist)。

最终实现

ts
  1. enum PromiseState {
  2. fulfilled = 'fulfilled',
  3. pending = 'pending',
  4. rejected = 'rejected'
  5. }
  6. type Executor<T> = (
  7. resolve: (value: T | PromiseLike<T>) => void,
  8. reject: (reason?: any) => void
  9. ) => void
  10. const scheduleMicrotask = (callback: () => void) => {
  11. if (typeof queueMicrotask === 'function') {
  12. queueMicrotask(callback)
  13. } else if (typeof process !== 'undefined' && process.nextTick) {
  14. process.nextTick(callback)
  15. } else {
  16. Promise.resolve().then(callback)
  17. }
  18. }
  19. class ShikaPromise<T = any> {
  20. private state: PromiseState = PromiseState.pending
  21. private value: T | undefined
  22. private reason: any
  23. private fulfilledHandlers: Array<() => void> = []
  24. private rejectedHandlers: Array<() => void> = []
  25. private isResolved = false
  26. constructor(executor: Executor<T>) {
  27. try {
  28. executor(
  29. (value) => this.resolve(value),
  30. (reason) => this.reject(reason)
  31. )
  32. } catch (error) {
  33. this.reject(error)
  34. }
  35. }
  36. private resolve(value: T | PromiseLike<T>): void {
  37. if (this.isResolved) return
  38. if (value === this) {
  39. this.reject(new TypeError('Cannot resolve promise with itself'))
  40. return
  41. }
  42. const thenable = this.getThenable(value)
  43. if (thenable) {
  44. this.resolveThenable(thenable)
  45. } else {
  46. this.fulfill(value as T)
  47. }
  48. }
  49. private fulfill(value: T): void {
  50. if (this.isResolved) return
  51. this.isResolved = true
  52. scheduleMicrotask(() => {
  53. this.state = PromiseState.fulfilled
  54. this.value = value
  55. const handlers = this.fulfilledHandlers.splice(0)
  56. handlers.forEach((h) => h())
  57. })
  58. }
  59. private reject(reason: any): void {
  60. if (this.isResolved) return
  61. this.isResolved = true
  62. scheduleMicrotask(() => {
  63. this.state = PromiseState.rejected
  64. this.reason = reason
  65. const handlers = this.rejectedHandlers.splice(0)
  66. handlers.forEach((h) => h())
  67. })
  68. }
  69. private getThenable(value: any): { then: Function; target: any } | null {
  70. if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
  71. try {
  72. const then = value.then
  73. if (typeof then === 'function') {
  74. return { then, target: value }
  75. }
  76. } catch (error) {
  77. this.reject(error)
  78. }
  79. }
  80. return null
  81. }
  82. private resolveThenable(thenable: { then: Function; target: any }): void {
  83. let called = false
  84. try {
  85. thenable.then.call(
  86. thenable.target,
  87. (value: any) => {
  88. if (called) return
  89. called = true
  90. this.resolve(value)
  91. },
  92. (reason: any) => {
  93. if (called) return
  94. called = true
  95. this.reject(reason)
  96. }
  97. )
  98. } catch (error) {
  99. if (!called) this.reject(error)
  100. }
  101. }
  102. then<TResult1 = T, TResult2 = never>(
  103. onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,
  104. onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined
  105. ): ShikaPromise<TResult1 | TResult2> {
  106. return new ShikaPromise<TResult1 | TResult2>((resolve, reject) => {
  107. const handleCallback = (isFulfilled: boolean) => {
  108. scheduleMicrotask(() => {
  109. const callback = isFulfilled ? onFulfilled : onRejected
  110. const data = isFulfilled ? this.value : this.reason
  111. if (typeof callback !== 'function') {
  112. if (isFulfilled) {
  113. resolve(data as TResult1)
  114. } else {
  115. reject(data)
  116. }
  117. return
  118. }
  119. try {
  120. const result = callback(data)
  121. resolve(result)
  122. } catch (error) {
  123. reject(error)
  124. }
  125. })
  126. }
  127. switch (this.state) {
  128. case PromiseState.fulfilled:
  129. handleCallback(true)
  130. break
  131. case PromiseState.rejected:
  132. handleCallback(false)
  133. break
  134. default:
  135. this.fulfilledHandlers.push(() => handleCallback(true))
  136. this.rejectedHandlers.push(() => handleCallback(false))
  137. }
  138. })
  139. }
  140. catch<TResult = never>(
  141. onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined
  142. ): ShikaPromise<T | TResult> {
  143. return this.then(null, onRejected)
  144. }
  145. finally(onFinally?: (() => void) | null | undefined): ShikaPromise<T> {
  146. return this.then(
  147. (value) => {
  148. onFinally?.()
  149. return value
  150. },
  151. (reason) => {
  152. onFinally?.()
  153. throw reason
  154. }
  155. )
  156. }
  157. static resolve<T>(value: T | PromiseLike<T>): ShikaPromise<T> {
  158. return value instanceof ShikaPromise ? value : new ShikaPromise((resolve) => resolve(value))
  159. }
  160. static reject<T = never>(reason?: any): ShikaPromise<T> {
  161. return new ShikaPromise((_, reject) => reject(reason))
  162. }
  163. static wait(): ShikaPromise<never> {
  164. return new ShikaPromise(() => {})
  165. }
  166. cancel(): ShikaPromise<never> {
  167. return new ShikaPromise(() => {})
  168. }
  169. static deferred<T>() {
  170. let resolve!: (value: T | PromiseLike<T>) => void
  171. let reject!: (reason?: any) => void
  172. const promise = new ShikaPromise<T>((res, rej) => {
  173. resolve = res
  174. reject = rej
  175. })
  176. return { promise, resolve, reject }
  177. }
  178. }
  179. module.exports = ShikaPromise

结尾

这里实现了一个 Promise A+ 规范的 Promise,重新理解 Promise A+ 规范也修复了我以前对此的认识不足之处。

0 条评论未登录用户
Ctrl or + Enter 评论
© 2023-2025 LittleRangiferTarandus, All rights reserved.
🌸 Run