news 2026/4/3 2:59:28

实现 Promise A+ 规范的 Promise

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
实现 Promise A+ 规范的 Promise

前言

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

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

这篇博客权当是复习吧...

简介 Promise A+ 规范

变量和术语

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

Promise 具有 3 种状态:pending(等待中)、fulfilled(成功执行)、rejected(失败拒绝),初始状态为 pending,切换为 fulfilled 或者 rejected 后就不能再转换。处于非 pending 状态时称为 settled。

const testPromise = new Promise((resolve, reject) => {

// DO SOMETHING

})

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

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

Promise A+ 规范的 Promise 上的方法只有简单的then,catch、finally之类的方法并不包含。

settled

not settled

调用resolve(value)

settled

not settled

抛出异常

调用reject(reason)

当前未执行resolve和reject,没有抛出异常

创建Promise

执行executor(resolve, reject)

executor执行结果?

Promise settled?

忽略重复调用

状态: pending → rejected

存储reason

Promise settled?

状态: pending → fulfilled

存储value

pending

then 方法

then方法具有onFulfilled和onRejected两个入参,返回一个 Promise(链式调用)。

举个栗子:

const temp = testPromise.then(function onFulfilled (value) {

// DO SOMETHING

}, function onRejected (reason) {

// DO SOMETHING

})

console.log(temp instanceof Promise) // true

console.log(temp === testPromise) // false

Promise 从 pending 状态切换到 fulfilled 或者 rejected 时,执行此前then传入的onFulfilled或onRejected。fulfilled 状态的 Promise 会执行then传入的onFulfilled,rejected 状态的 Promise 会执行then传入的onRejected。

执行onFulfilled或onRejected的结果会被传入新的 Promise temp的resolve方法中,如果发生了错误则传入reject中,改变的状态和数据。

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

用一个流程图总结一下:

pending

fulfilled

rejected

正常返回

正常返回

抛出异常

抛出异常

状态变为fulfilled

状态变为rejected

调用promise.then(onFulfilled, onRejected)

当前状态?

注册回调到队列

等待状态改变

异步执行onFulfilled(value)

异步执行onRejected(reason)

onFulfilled返回值?

onRejected返回值?

Promise Resolution Procedure

调用新Promise的reject

状态: rejected

返回新Promise

Promise Resolution Procedure

resolve被触发时发生什么事了?此时 Promise 的状态仍未真正变化,会进入一段处理程序,规范称之为 Promise Resolution Procedure,主要逻辑是如果传入的是非 thenable 对象或者基本类型则直接修改 Promise 的状态和数据,是 thenable 就执行下面 thenable 相关逻辑。

此外,不支持我返回我自己,onFulfilled或者onRejected返回该then返回的 Promise 时,抛出TypeError错误,例如:

const temp = testPromise.then(function onFulfilled (value) {

return temp

})

处理 thenable 对象

thenable 的对象是具有then方法的对象或者函数。then方法接受两个回调函数onResolvePromise和onRejectPromise,类似于这里的 Promise 的then。thenable 实际上包括实现了 Promise A+ 规范的 Promise,例如 ES6 原生的 Promise。举个 thenable 对象的栗子:

const thenable = {

then: function (onResolvePromise, onRejectPromise) {

onResolvePromise('miao~~')

}

}

如果触发了onFulfilled,返回了一个 thenable。如果是该 Promise 的实例,不是当前 Promise,则传入当前 Promise 的resolve和reject,调用then方法。

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

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

其他详细见 Promise A+ 规范。

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

调用resolvePromise(x)

调用rejectPromise(reason)

抛出异常

not settled

not settled

settled

settled

Promise Resolution Procedure

返回值是thenable?

是否返回自身?

抛出TypeError

调用thenable.then(resolvePromise, rejectPromise)

调用新Promise的resolve

状态: fulfilled

thenable行为?

Promise settled?

Promise settled?

调用新Promise的reject

状态: rejected

状态: fulfilled

value = x

状态: rejected

reason = reason

忽略重复调用

返回新Promise

返回rejected Promise

reason = TypeError

前期准备

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

enum PromiseState {

fulfilled = 'fulfilled',

pending = 'pending',

rejected = 'rejected'

}

type Executor<T> = (

resolve: (value: T | PromiseLike<T>) => void,

reject: (reason?: any) => void

) => void

const scheduleMicrotask = (callback: () => void) => {

if (typeof queueMicrotask === 'function') {

queueMicrotask(callback)

} else if (typeof process !== 'undefined' && process.nextTick) {

process.nextTick(callback)

} else {

Promise.resolve().then(callback)

}

}

简单地写一个 Promise

class ShikaPromise<T = any> {

private state: PromiseState = PromiseState.pending

private value: T | undefined

private reason: any

constructor(executor: Executor<T>) {

try {

executor(

(value) => this.resolve(value),

(reason) => this.reject(reason)

)

} catch (error) {

this.reject(error)

}

}

private resolve(value: T): void {

// 不支持 resolve 自己

if (value === this) {

this.reject(new TypeError('Cannot resolve promise with itself'))

return

}

scheduleMicrotask(() => {

this.state = PromiseState.fulfilled

this.value = value

})

}

private reject(reason: any): void {

scheduleMicrotask(() => {

this.state = PromiseState.rejected

this.reason = reason

})

}

then<TResult1 = T, TResult2 = never>(

onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,

onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined

) {

// TODO

}

}

下面就来写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 的,则在新的promise2的executor中的resolve传入onFulfilled的结果,如果promise1处于失败状态,rejected 了,则在promise2的resolve中传入onRejected的结果。

举个栗子:

const promiseTmp1 = Promise.resolve('ok').then(value => value, reason => reason)

// 此时 promiseTmp1.value 是 'ok'

const promiseTmp2 = Promise.resolve('error').then(value => value, reason => reason)

// 此时 promiseTmp2.value 是 'error'

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

// ...

class ShikaPromise {

// ...

then<TResult1 = T, TResult2 = never>(

onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,

onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined

): ShikaPromise<TResult1 | TResult2> {

return new ShikaPromise<TResult1 | TResult2>((resolve, reject) => {

const handleCallback = (isFulfilled: boolean) => {

scheduleMicrotask(() => {

const callback = isFulfilled ? onFulfilled : onRejected

const data = isFulfilled ? this.value : this.reason

// 传值穿透

if (typeof callback !== 'function') {

if (isFulfilled) {

resolve(data as TResult1)

} else {

reject(data)

}

return

}

try {

const result = callback(data)

resolve(result)

} catch (error) {

reject(error)

}

})

}

switch (this.state) {

case PromiseState.fulfilled:

handleCallback(true)

break

case PromiseState.rejected:

handleCallback(false)

break

default:

// TODO

}

})

}

}

pending 状态

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

// ...

class ShikaPromise {

// ...

// 记录等待 fulfilled 或者 rejected 后执行的回调函数

private fulfilledHandlers: Array<() => void> = []

private rejectedHandlers: Array<() => void> = []

// ...

private resolve(value: T): void {

scheduleMicrotask(() => {

this.state = PromiseState.fulfilled

this.value = value

const handlers = this.fulfilledHandlers.splice(0)

handlers.forEach((h) => h())

})

}

private reject(reason: any): void {

scheduleMicrotask(() => {

this.state = PromiseState.rejected

this.reason = reason

const handlers = this.rejectedHandlers.splice(0)

handlers.forEach((h) => h())

})

}

// ...

then (onFulfilled?: ThenCallback, onRejected?: ThenCallback) {

return new ShikaPromise<TResult1 | TResult2>((resolve, reject) => {

// ...

switch (this.state) {

// ...

default:

this.fulfilledHandlers.push(() => handleCallback(true))

this.rejectedHandlers.push(() => handleCallback(false))

}

})

}

}

防止多次触发

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

// ...

class ShikaPromise<T = any> {

// ...

private isResolved = false

// ...

private resolve(value: T | PromiseLike<T>): void {

if (this.isResolved) return

if (value === this) {

this.reject(new TypeError('Cannot resolve promise with itself'))

return

}

// TODO: thenable 处理

this.fulfill(value as T)

}

private fulfill(value: T): void {

if (this.isResolved) return

this.isResolved = true

scheduleMicrotask(() => {

this.state = PromiseState.fulfilled

this.value = value

const handlers = this.fulfilledHandlers.splice(0)

handlers.forEach((h) => h())

})

}

private reject(reason: any): void {

if (this.isResolved) return

this.isResolved = true

// ...

}

// ...

}

解析 thenable 对象

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

class ShikaPromise<T = any> {

// ...

private resolve(value: T | PromiseLike<T>): void {

// ...

const thenable = this.getThenable(value)

if (thenable) {

this.resolveThenable(thenable)

} else {

this.fulfill(value as T)

}

}

private getThenable(value: any): { then: Function; target: any } | null {

if (value !== null && (typeof value === 'object' || typeof value === 'function')) {

try {

// 在规范中有 Let then be x.then 的描述,测试用例中 value.then 只能被取一次

const then = value.then

if (typeof then === 'function') {

return { then, target: value }

}

} catch (error) {

this.reject(error)

}

}

return null

}

private resolveThenable(thenable: { then: Function; target: any }): void {

let called = false

try {

thenable.then.call(

thenable.target,

(value: any) => {

if (called) return

called = true

this.resolvevaluey)

},

(reason: any) => {

if (called) return

called = true

this.reject(reason)

}

)

} catch (error) {

if (!called) this.reject(error)

}

}

}

其他方法

JS 的 Promise

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

class ShikaPromise {

catch<TResult = never>(

onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined

): ShikaPromise<T | TResult> {

return this.then(null, onRejected)

}

finally(onFinally?: (() => void) | null | undefined): ShikaPromise<T> {

return this.then(

(value) => {

onFinally?.()

return value

},

(reason) => {

onFinally?.()

throw reason

}

)

}

}

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

class ShikaPromise {

static resolve<T>(value: T | PromiseLike<T>): ShikaPromise<T> {

return value instanceof ShikaPromise ? value : new ShikaPromise((resolve) => resolve(value))

}

static reject<T = never>(reason?: any): ShikaPromise<T> {

return new ShikaPromise((_, reject) => reject(reason))

}

}

如果 Promise 可以停止

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

class ShikaPromise {

static wait(): ShikaPromise<never> {

return new ShikaPromise(() => {})

}

cancel(): ShikaPromise<never> {

return new ShikaPromise(() => {})

}

}

Promise A+ 测试

下载 promises-aplus-tests 包:

npm i promises-aplus-tests

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

class ShikaPromise {

static deferred<T>() {

let resolve!: (value: T | PromiseLike<T>) => void

let reject!: (reason?: any) => void

const promise = new ShikaPromise<T>((res, rej) => {

resolve = res

reject = rej

})

return { promise, resolve, reject }

}

}

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

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

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

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

{

// ...

"scripts": {

// ...

"test": "tsc && promises-aplus-tests dist/文件名.js",

},

// ...

}

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

最终实现

enum PromiseState {

fulfilled = 'fulfilled',

pending = 'pending',

rejected = 'rejected'

}

type Executor<T> = (

resolve: (value: T | PromiseLike<T>) => void,

reject: (reason?: any) => void

) => void

const scheduleMicrotask = (callback: () => void) => {

if (typeof queueMicrotask === 'function') {

queueMicrotask(callback)

} else if (typeof process !== 'undefined' && process.nextTick) {

process.nextTick(callback)

} else {

Promise.resolve().then(callback)

}

}

class ShikaPromise<T = any> {

private state: PromiseState = PromiseState.pending

private value: T | undefined

private reason: any

private fulfilledHandlers: Array<() => void> = []

private rejectedHandlers: Array<() => void> = []

private isResolved = false

constructor(executor: Executor<T>) {

try {

executor(

(value) => this.resolve(value),

(reason) => this.reject(reason)

)

} catch (error) {

this.reject(error)

}

}

private resolve(value: T | PromiseLike<T>): void {

if (this.isResolved) return

if (value === this) {

this.reject(new TypeError('Cannot resolve promise with itself'))

return

}

const thenable = this.getThenable(value)

if (thenable) {

this.resolveThenable(thenable)

} else {

this.fulfill(value as T)

}

}

private fulfill(value: T): void {

if (this.isResolved) return

this.isResolved = true

scheduleMicrotask(() => {

this.state = PromiseState.fulfilled

this.value = value

const handlers = this.fulfilledHandlers.splice(0)

handlers.forEach((h) => h())

})

}

private reject(reason: any): void {

if (this.isResolved) return

this.isResolved = true

scheduleMicrotask(() => {

this.state = PromiseState.rejected

this.reason = reason

const handlers = this.rejectedHandlers.splice(0)

handlers.forEach((h) => h())

})

}

private getThenable(value: any): { then: Function; target: any } | null {

if (value !== null && (typeof value === 'object' || typeof value === 'function')) {

try {

const then = value.then

if (typeof then === 'function') {

return { then, target: value }

}

} catch (error) {

this.reject(error)

}

}

return null

}

private resolveThenable(thenable: { then: Function; target: any }): void {

let called = false

try {

thenable.then.call(

thenable.target,

(value: any) => {

if (called) return

called = true

this.resolve(value)

},

(reason: any) => {

if (called) return

called = true

this.reject(reason)

}

)

} catch (error) {

if (!called) this.reject(error)

}

}

then<TResult1 = T, TResult2 = never>(

onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,

onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined

): ShikaPromise<TResult1 | TResult2> {

return new ShikaPromise<TResult1 | TResult2>((resolve, reject) => {

const handleCallback = (isFulfilled: boolean) => {

scheduleMicrotask(() => {

const callback = isFulfilled ? onFulfilled : onRejected

const data = isFulfilled ? this.value : this.reason

if (typeof callback !== 'function') {

if (isFulfilled) {

resolve(data as TResult1)

} else {

reject(data)

}

return

}

try {

const result = callback(data)

resolve(result)

} catch (error) {

reject(error)

}

})

}

switch (this.state) {

case PromiseState.fulfilled:

handleCallback(true)

break

case PromiseState.rejected:

handleCallback(false)

break

default:

this.fulfilledHandlers.push(() => handleCallback(true))

this.rejectedHandlers.push(() => handleCallback(false))

}

})

}

catch<TResult = never>(

onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined

): ShikaPromise<T | TResult> {

return this.then(null, onRejected)

}

finally(onFinally?: (() => void) | null | undefined): ShikaPromise<T> {

return this.then(

(value) => {

onFinally?.()

return value

},

(reason) => {

onFinally?.()

throw reason

}

)

}

static resolve<T>(value: T | PromiseLike<T>): ShikaPromise<T> {

return value instanceof ShikaPromise ? value : new ShikaPromise((resolve) => resolve(value))

}

static reject<T = never>(reason?: any): ShikaPromise<T> {

return new ShikaPromise((_, reject) => reject(reason))

}

static wait(): ShikaPromise<never> {

return new ShikaPromise(() => {})

}

cancel(): ShikaPromise<never> {

return new ShikaPromise(() => {})

}

static deferred<T>() {

let resolve!: (value: T | PromiseLike<T>) => void

let reject!: (reason?: any) => void

const promise = new ShikaPromise<T>((res, rej) => {

resolve = res

reject = rej

})

return { promise, resolve, reject }

}

}

module.exports = ShikaPromise

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/23 7:39:02

紫金桥组态软件的功能特点及实际优势

一、核心功能特点1. 数据采集与通信能力紫金桥组态软件支持多种工业通信协议&#xff0c;包括Modbus、OPC UA、OPC DA等主流协议&#xff0c;能够与国内外常见的PLC、DCS、智能仪表等设备进行稳定通信。软件的数据采集周期可配置&#xff0c;最短可达毫秒级&#xff0c;满足多数…

作者头像 李华
网站建设 2026/3/24 23:35:17

宇树 Qmini 双足机器人训练个人经验总结

前提说明&#xff1a;为什么不建议在云端直接跑渲染&#xff1f;#我最开始的目标是&#xff1a;训练、渲染、视频录制全部在 AutoDL 上完成&#xff0c;不经过本地运行。然而现实是&#xff1a;即使用 Xvfb 等虚拟显示器启动 Isaac Gym&#xff0c;也会发生视频保存全黑的情况。…

作者头像 李华
网站建设 2026/3/19 17:58:36

【Java】浅谈synchronized与ReentrantLock

目录澄清误解synchronized 与 ReentrantLock对比乐观锁 vs 悲观锁公平锁 vs 非公平锁synchronized的锁升级ReentrantLock的CLH队列可重入与CAS的关系总结前言&#xff1a; 上一篇在对比锁与volatile机制的时候&#xff0c;因为没有太多考虑synchronized 和ReentrantLock的区分&…

作者头像 李华
网站建设 2026/3/30 5:41:10

Winlator安卓神器:手机秒变Windows电脑的7大实战技巧

Winlator安卓神器&#xff1a;手机秒变Windows电脑的7大实战技巧 【免费下载链接】winlator Android application for running Windows applications with Wine and Box86/Box64 项目地址: https://gitcode.com/GitHub_Trending/wi/winlator 还在为手机无法运行Windows应…

作者头像 李华
网站建设 2026/3/29 0:22:37

如何快速掌握StarRocks:索引机制深度解析与性能优化实战指南

如何快速掌握StarRocks&#xff1a;索引机制深度解析与性能优化实战指南 【免费下载链接】starrocks StarRocks是一个开源的分布式数据分析引擎&#xff0c;用于处理大规模数据查询和分析。 - 功能&#xff1a;分布式数据分析&#xff1b;大规模数据查询&#xff1b;数据分析&a…

作者头像 李华
网站建设 2026/4/1 0:09:49

Cap录屏神器终极使用宝典:快速上手到专业录制

还在为录制屏幕视频而头疼吗&#xff1f;传统录屏软件要么收费昂贵&#xff0c;要么操作复杂&#xff0c;要么水印烦人。Cap作为一款开源跨平台的视频录制工具&#xff0c;彻底解决了这些痛点。无论你是需要制作在线教学视频、产品演示还是技术分享&#xff0c;Cap都能轻松应对…

作者头像 李华