Promise
题目描述
实现一个自定义 Promise 类 (MyPromise),聚焦基础 Promise/A+ 行为:
- 状态管理:
pending → fulfilled | rejected单向不可逆 - 链式调用:
then / catch / finally - 异步调度:回调通过微任务(优先
queueMicrotask)推迟执行 - thenable 展开:递归解析符合 thenable 形态的对象
- 错误传播与值透传:非函数处理器的穿透语义
⚠ 当前代码仅实现 static resolve / static reject,没有实现文档最初要求的 all / race;finally 行为与原生仍存在差异(不会等待其返回的 Promise)。本文件以下内容已“按代码真实行为”重写,并标注与规范的差异与可改进点。
核心知识点
状态机核心与内部字段
javascript
const PENDING = 'pending' // 等待中:初始状态
const FULFILLED = 'fulfilled' // 已成功:操作成功完成
const REJECTED = 'rejected' // 已拒绝:操作失败内部私有字段:
| 字段 | 含义 |
|---|---|
#state | 当前状态:pending / fulfilled / rejected |
#result | 已决议结果(成功值或拒因) |
#handlers | 待处理的回调队列(元素含 onFulfilled / onRejected / resolve / reject) |
状态转换规则:
- 仅允许
pending → fulfilled | rejected - 一旦确定不再改变(靠状态判定短路)
- 解析 thenable 时递归跟随直到得到普通值或错误
链式调用与值/错透传
then(onFulfilled, onRejected):立即将处理器对象压入 #handlers,随后调用 #run() 判断当前状态是否已决议:
- 若仍是
pending:队列静置 - 若已决议:同步触发
#run()内的 while 循环,将每个处理排入微任务(真正用户回调仍是异步)
透传逻辑(在 #runOne 内):
- 如果对应回调不是函数:
- fulfilled → 直接
resolve(this.#result) - rejected → 直接
reject(this.#result)(形成错误冒泡)
- fulfilled → 直接
链式创建:then 返回新的 MyPromise,由子 promise 的 executor 中捕获上层回调执行结果并调用其 resolve / reject。
微任务调度策略
优先级(按代码实际顺序):
queueMicrotaskprocess.nextTickMutationObserverhacksetTimeout兜底(宏任务)
所有注册回调最终在 #runMicroTask 包裹的回调里执行,保证符合“异步执行”要求(A+:必须在当前执行栈完成后)。
thenable 解析与循环引用防护
isPromiseLike(x):判断对象/函数且含then方法- 若
x === this:触发TypeError('Chaining cycle detected') - 若是 thenable:调用其
then,成功分支递归#resolvePromise,失败分支直接#changeState(REJECTED,r) - 解析过程中依赖状态不可逆性防止多次决议;未显式实现“多次调用 then 防二次执行”锁,但状态切换后后续调用会被忽略(满足规范要求)
finally 实现特性
js
finally(cb) {
return this.then(
v => { cb && cb(); return v },
e => { cb && cb(); throw e }
)
}差异:若 cb 返回一个 Promise,原生需等待其完成再继续链路;此实现直接忽略返回值,不等待。
静态方法行为
| 方法 | 当前实现 | 与原生差异 |
|---|---|---|
resolve(v) | 始终 new MyPromise(resolve => resolve(v)) | 原生:若 v 已是同源 Promise 直接返回它(短路) |
reject(r) | new MyPromise((_, reject) => reject(r)) | 与原生等价语义 |
all | 未实现 | —— |
race | 未实现 | —— |
allSettled | 未实现 | —— |
any | 未实现 | —— |
错误传播与捕获
- executor 内同步异常 → try/catch →
reject(e) - thenable
then执行抛错 → 捕获并拒绝 - 用户回调抛错 → 在
#runOnetry/catch 捕获并reject - 非函数处理器时错误透传不被吞掉(保持冒泡)
与 PromiseA+ / 原生 Promise 的主要差异
| 维度 | 当前实现 | 差异说明/后果 |
|---|---|---|
| finally 等待 | 不等待返回的 Promise | 可能导致资源清理未完成就进入后续链 |
| Promise.resolve | 始终包装 | 失去同一个实例的快捷路径,轻微性能损耗 |
| queue 触发时机 | then 已决议时同步调用 #run() | 行为允许(内部回调仍是异步) |
| 长链内存 | handlers shift 一个一个出队 | O(n) shift 可能有性能影响(可改成索引递增) |
真实行为快速示例
javascript
import MyPromise from './MyPromise'
MyPromise.resolve(1)
.then(v => v + 1)
.finally(() => new Promise(r => setTimeout(r, 100))) // 不会等待 100ms
.then(v => console.log('=>', v)) // 立即输出 2(原生需等待 finally 完成)
new MyPromise((resolve) => {
resolve(MyPromise.resolve(42)) // 嵌套 thenable 展开
}).then(v => console.log(v)) // 42改进方向(按优先级)
- 实现
static all / race / allSettled / any finally支持等待其返回值(若为 Promise)Promise.resolve短路逻辑(若为 MyPromise 实例直接返回)- handlers 队列改为索引遍历避免
shift频繁移动 - 增加“未处理拒绝”检测(unhandled rejection 报告)
- 增加调用保护:thenable 的
then只调用一次(标志位) - 支持
MyPromise.deferred()(便于 A+ 官方测试套) - 增加开发模式下的长堆栈追踪(链式错误排查)
微任务实现优先级
javascript
// 1. Node.js 环境:process.nextTick(最高优先级)
// 2. 浏览器环境:MutationObserver
// 3. 兜底方案:setTimeout(宏任务,不符合规范但可用)
function nextTick(fn) {
if (typeof process !== 'undefined' && process.nextTick) {
process.nextTick(fn)
}
else if (typeof MutationObserver !== 'undefined') {
const observer = new MutationObserver(fn)
const textNode = document.createTextNode('1')
observer.observe(textNode, { characterData: true })
textNode.data = '2'
}
else {
setTimeout(fn, 0)
}
}then 方法的复杂处理逻辑
- thenable 对象识别: 有 then 方法的对象
- 循环引用检测: 防止 Promise 解析自身
- 异常捕获: try-catch 包裹所有用户代码
- 状态同步: 处理已决议 Promise 的立即执行
代码实现(当前版本)
javascript
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
export default class MyPromise {
#state = PENDING
#result = void 0
#handlers = []
constructor(executor) {
let sealed = false // 只允许第一次生效
const resolve = (x) => {
if (sealed)
return
sealed = true
this.#resolvePromise(x)
}
const reject = (r) => {
if (sealed)
return
sealed = true
this.#changeState(REJECTED, r)
}
try {
executor(resolve, reject)
}
catch (e) {
reject(e)
}
}
// ===== 内部工具 =====
isPromiseLike(value) {
return (
value !== null
&& (typeof value === 'object' || typeof value === 'function')
&& typeof value.then === 'function'
)
}
#runMicroTask(fn) {
if (typeof queueMicrotask === 'function') {
queueMicrotask(fn)
}
else if (typeof process === 'object' && typeof process.nextTick === 'function') {
process.nextTick(fn)
}
else if (typeof MutationObserver === 'function') {
const ob = new MutationObserver(fn)
const text = document.createTextNode('1')
ob.observe(text, { characterData: true })
text.data = '2'
}
else {
setTimeout(fn, 0)
}
}
#changeState(state, result) {
if (this.#state !== PENDING)
return
this.#state = state
this.#result = result
this.#run()
}
// A+ 2.3: [[Resolve]](promise, x)
#resolvePromise(x) {
if (this.#state !== PENDING)
return
if (x === this) {
this.#changeState(REJECTED, new TypeError('Chaining cycle detected'))
return
}
if (this.isPromiseLike(x)) {
// 跟随 thenable
try {
x.then(
y => this.#resolvePromise(y), // 递归展开
r => this.#changeState(REJECTED, r),
)
}
catch (e) {
this.#changeState(REJECTED, e)
}
return
}
// 普通值
this.#changeState(FULFILLED, x)
}
#runOne(callback, resolve, reject) {
this.#runMicroTask(() => {
if (typeof callback !== 'function') {
// 值透传/错透传
if (this.#state === FULFILLED)
resolve(this.#result)
else reject(this.#result)
return
}
try {
const data = callback(this.#result)
// 这里不直接展开,由子 promise 的 resolve 去执行 [[Resolve]]
resolve(data)
}
catch (err) {
reject(err)
}
})
}
#run() {
if (this.#state === PENDING)
return
while (this.#handlers.length) {
const { onFulfilled, onRejected, resolve, reject } = this.#handlers.shift()
if (this.#state === FULFILLED) {
this.#runOne(onFulfilled, resolve, reject)
}
else {
this.#runOne(onRejected, resolve, reject)
}
}
}
// ===== 对外 API =====
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#handlers.push({ onFulfilled, onRejected, resolve, reject })
this.#run()
})
}
catch(onRejected) {
return this.then(undefined, onRejected)
}
finally(onFinally) {
return this.then(
(v) => {
if (typeof onFinally === 'function')
onFinally()
return v
},
(e) => {
if (typeof onFinally === 'function')
onFinally()
throw e
},
)
}
static resolve(value) {
// 与原生差异:不会短路已是 MyPromise 的情况
return new MyPromise(resolve => resolve(value))
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason))
}
}规范差异详解
| 规范条目 | 要求 | 当前实现 | 影响 |
|---|---|---|---|
| A+ 2.2.4 回调必须异步 | 回调排入微任务 | 使用 queueMicrotask/降级 | 符合 |
| A+ 2.2.6 多次调用忽略 | 依赖状态短路 | 满足 | ✅ |
| A+ 2.3 解析 thenable | 递归调用 x.then | 有 | ✅ |
| finally 等待 | 等待返回值 Promise | 未等待 | 可能过早继续 |
| Promise.resolve 行为 | 同实例短路 | 未短路 | 轻微性能差异 |
| all / race | 提供 | 未实现 | 功能缺失 |
| 未处理拒绝事件 | 可监控 | 未提供 | 调试不便 |
常见陷阱与排查提示
| 场景 | 现象 | 诊断建议 |
|---|---|---|
| finally 中返回异步 | 后续 then 立即执行 | 标注语义差异或改造 finally |
| 大量链式 then | 性能下降 | 优化 handlers 结构(索引遍历) |
| thenable 实现不规范(多次调用) | 只第一次生效 | 符合预期,无需额外处理 |
| 用户抛同步异常 | 进入 rejected | 依赖 try/catch 已覆盖 |
| 忘记写返回值 | 透传上一层值 | 符合透传语义 |
改进示例
finally 规范化(等待返回 Promise)
javascript
finally(onFinally) {
return this.then(
v => MyPromise.resolve(typeof onFinally === 'function' && onFinally())
.then(() => v),
e => MyPromise.resolve(typeof onFinally === 'function' && onFinally())
.then(() => { throw e })
)
}resolve 短路已有 Promise
javascript
static resolve(value) {
if (value instanceof MyPromise) return value
return new MyPromise(r => r(value))
}索引遍历替换 shift(避免 O(n^2) 移动成本)
javascript
#run() {
if (this.#state === PENDING) return
let i = 0
while (i < this.#handlers.length) {
const h = this.#handlers[i++]
this.#runOne(
this.#state === FULFILLED ? h.onFulfilled : h.onRejected,
h.resolve,
h.reject,
)
}
this.#handlers.length = 0
}all 简易实现(示例)
javascript
static all(iterable) {
return new MyPromise((resolve, reject) => {
const results = []
let count = 0
let total = 0
for (const item of iterable) {
const idx = total++
MyPromise.resolve(item).then(
v => {
results[idx] = v
if (++count === total) resolve(results)
},
reject,
)
}
if (total === 0) resolve([])
})
}练习与自测建议
| 目标 | 建议用例 |
|---|---|
| 值/错透传 | Promise.resolve(1).then().then(v=>v+1) |
| thenable 展开 | 手写 { then(res){ res(123) } } |
| 循环引用 | const p = new MyPromise(r=>r()); p.then(()=>p) |
| finally 差异 | finally 返回 new Promise(r=>setTimeout(r,100)) |
| 多次决议忽略 | executor 中 resolve(1); resolve(2); reject(3) |
总结
- 状态只改一次:
#changeState+ 早退判定 - 解析 thenable:递归
x.then(y => #resolvePromise(y)) - 回调异步:统一
#runMicroTask排队 - 透传策略:非函数处理器直接走同向/反向
- finally 当前“不等待”→ 可改造成等待版本
- 缺失
all/race,可按示例扩展 - 队列结构可优化防 shift 性能损耗
- 可添加 unhandled rejection 监控提升可观测性
扩展思考
1. 支持取消的 Promise
javascript
class CancellablePromise extends MyPromise {
constructor(executor) {
let isCancelled = false
let cancelCallback = null
super((resolve, reject) => {
const wrappedResolve = (value) => {
if (!isCancelled)
resolve(value)
}
const wrappedReject = (reason) => {
if (!isCancelled)
reject(reason)
}
cancelCallback = executor(wrappedResolve, wrappedReject)
})
this.cancel = () => {
isCancelled = true
if (typeof cancelCallback === 'function') {
cancelCallback()
}
}
}
}
// 使用示例
const cancellable = new CancellablePromise((resolve, reject) => {
const timer = setTimeout(() => resolve('completed'), 1000)
// 返回取消函数
return () => clearTimeout(timer)
})
setTimeout(() => cancellable.cancel(), 500) // 取消执行2. 支持重试的 Promise
javascript
class RetryablePromise extends MyPromise {
static withRetry(promiseFactory, maxRetries = 3, delay = 1000) {
return new MyPromise(async (resolve, reject) => {
let lastError
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const result = await promiseFactory()
resolve(result)
return
}
catch (error) {
lastError = error
if (attempt < maxRetries) {
await new MyPromise(r => setTimeout(r, delay * 2 ** attempt))
}
}
}
reject(lastError)
})
}
}
// 使用示例
const fetchWithRetry = RetryablePromise.withRetry(
() => fetch('/api/data'),
3,
1000,
)3. 支持超时的 Promise
javascript
class TimeoutPromise extends MyPromise {
static withTimeout(promise, timeout, timeoutMessage = 'Operation timeout') {
return MyPromise.race([
promise,
new MyPromise((_, reject) => {
setTimeout(() => reject(new Error(timeoutMessage)), timeout)
}),
])
}
}
// 使用示例
const promiseWithTimeout = TimeoutPromise.withTimeout(
fetch('/api/slow-endpoint'),
5000,
'Request timeout after 5 seconds',
)4. 支持进度的 Promise
javascript
class ProgressPromise extends MyPromise {
constructor(executor) {
const progressCallbacks = []
super((resolve, reject) => {
const progress = (value) => {
progressCallbacks.forEach(callback => callback(value))
}
executor(resolve, reject, progress)
})
this.onProgress = (callback) => {
progressCallbacks.push(callback)
return this
}
}
}
// 使用示例
const uploadFile = new ProgressPromise((resolve, reject, progress) => {
// 模拟文件上传
let uploaded = 0
const total = 100
const interval = setInterval(() => {
uploaded += 10
progress((uploaded / total) * 100)
if (uploaded >= total) {
clearInterval(interval)
resolve('Upload completed')
}
}, 100)
})
uploadFile
.onProgress(percent => console.log(`Upload progress: ${percent}%`))
.then(result => console.log(result))5. 支持管道的 Promise
javascript
class PipelinePromise extends MyPromise {
static pipeline(...functions) {
return (initialValue) => {
return functions.reduce(
(promise, fn) => promise.then(fn),
MyPromise.resolve(initialValue),
)
}
}
pipe(fn) {
return this.then(fn)
}
}
// 使用示例
const processData = PipelinePromise.pipeline(
data => data.trim(),
data => data.toUpperCase(),
data => data.split(' '),
data => data.filter(word => word.length > 3),
)
processData(' hello world programming ').then(result =>
console.log(result),
) // ['HELLO', 'WORLD', 'PROGRAMMING']
// 链式管道
MyPromise.resolve('initial data')
.pipe(data => data.toUpperCase())
.pipe(data => data.split(' '))
.pipe(data => data.join('-'))
.then(result => console.log(result)) // 'INITIAL-DATA'