一、什么是 Promise?
Promise 是 JavaScript 中处理异步操作的解决方案。想象你叫外卖:
-
你下单(创建 Promise)
-
等待餐品制作(pending 状态)
-
收到外卖(fulfilled 状态)或配送失败(rejected 状态)
Promise 的三种状态:
状态 | 说明 | 转换规则 |
---|---|---|
pending | 等待中(初始状态) | 可转换为其他状态 |
fulfilled | 已成功 | 不可逆 |
rejected | 已失败 | 不可逆 |
二、手写 Promise 分步实现
1. 基础框架搭建
class MyPromise {
constructor(executor) {
this.state = 'pending' // 初始状态
this.value = null // 成功值
this.reason = null // 失败原因
this.onFulfilledCallbacks = [] // 成功回调队列
this.onRejectedCallbacks = [] // 失败回调队列
// 成功回调
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn())
}
}
// 失败回调
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject) // 立即执行传入的函数
} catch (err) {
reject(err) // 捕获同步错误
}
}
}
重点解析:
-
executor
是立即执行的函数,接收resolve
和reject
两个参数 -
使用队列保存回调函数,确保异步执行顺序
-
try...catch
捕获执行器中的同步错误
2. 实现 then 方法
then(onFulfilled, onRejected) {
// 处理值穿透(当参数不是函数时)
onFulfilled = typeof onFulfilled === 'function'
? onFulfilled
: value => value
onRejected = typeof onRejected === 'function'
? onRejected
: reason => { throw reason }
// 创建新 Promise 用于链式调用
const promise2 = new MyPromise((resolve, reject) => {
// 统一处理函数
const handle = (callback, value) => {
setTimeout(() => { // 模拟微任务
try {
const x = callback(value)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
if (this.state === 'fulfilled') {
handle(onFulfilled, this.value)
} else if (this.state === 'rejected') {
handle(onRejected, this.reason)
} else { // pending 状态
this.onFulfilledCallbacks.push(() => {
handle(onFulfilled, this.value)
})
this.onRejectedCallbacks.push(() => {
handle(onRejected, this.reason)
})
}
})
return promise2
}
关键点解释:
-
值穿透:当
then
的参数不是函数时,会创建默认函数传递值/抛出错误 -
异步执行:使用
setTimeout
模拟微任务队列 -
链式调用:每个
then
都返回新 Promise 对象
3. 实现 resolvePromise
function resolvePromise(promise2, x, resolve, reject) {
// 循环引用检查
if (promise2 === x) {
return reject(new TypeError('循环引用'))
}
let called = false // 防止多次调用
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
const then = x.then
if (typeof then === 'function') { // 判断是否为 Promise
then.call(
x,
y => { // 成功回调
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
},
r => { // 失败回调
if (called) return
called = true
reject(r)
}
)
} else { // 普通对象
resolve(x)
}
} catch (err) { // 捕获访问 then 属性的错误
if (called) return
called = true
reject(err)
}
} else { // 基本类型值
resolve(x)
}
}
为什么需要这个函数?
-
处理不同返回值类型(普通值/Promise 对象)
-
递归解析嵌套 Promise
-
防止循环引用导致死循环
三、使用案例详解
案例 1:基础链式调用
const p = new MyPromise((resolve) => {
setTimeout(() => resolve(100), 1000)
})
p.then(res => {
console.log('第一次处理:', res) // 1秒后输出 100
return res * 2
}).then(res => {
console.log('第二次处理:', res) // 输出 200
})
执行流程:
-
创建 Promise 对象,1秒后 resolve
-
第一个 then 接收 100,返回 200
-
第二个 then 接收 200 进行输出
案例 2:错误处理
new MyPromise((resolve, reject) => {
setTimeout(() => reject(new Error('网络错误')), 500)
})
.then(null, err => {
console.log('捕获错误:', err.message) // 输出 "网络错误"
return '默认值'
})
.then(res => {
console.log('后续处理:', res) // 输出 "默认值"
})
流程说明:
-
Promise 在 0.5 秒后 reject
-
第一个 then 的第二个参数捕获错误
-
返回新值传递给后续 then
案例 3:静态方法
// 并行执行多个 Promise
MyPromise.all([
new MyPromise(r => setTimeout(() => r(1), 300)),
new MyPromise(r => setTimeout(() => r(2), 200)),
3 // 普通值会自动转换为 resolved Promise
]).then(res => {
console.log('all 结果:', res) // 约300ms后输出 [1, 2, 3]
})
// 竞速执行
MyPromise.race([
new MyPromise(r => setTimeout(() => r('快'), 100)),
new MyPromise(r => setTimeout(() => r('慢'), 200))
]).then(res => {
console.log('race 结果:', res) // 约100ms后输出 "快"
})
方法对比:
方法 | 特点 | 适用场景 |
---|---|---|
all | 等待全部完成 | 需要收集多个异步结果 |
race | 采用第一个完成的结果 | 超时控制、竞速请求 |
resolve | 快速创建成功 Promise | 包装已知值 |
reject | 快速创建失败 Promise | 抛出已知错误 |
四、常见问题解答
Q1: 为什么要用 setTimeout?
-
原生 Promise 使用微任务队列(microtask)
-
这里用宏任务模拟实现异步效果
-
实际开发中应使用 queueMicrotask
Q2: 如何实现真正的微任务?
// 替换 handle 函数中的 setTimeout
if (typeof queueMicrotask === 'function') {
queueMicrotask(() => { /* 逻辑 */ })
} else {
Promise.resolve().then(() => { /* 逻辑 */ })
}
Q3: 为什么要有 called 标志?
防止以下情况:
const thenable = {
then: (resolve, reject) => {
resolve(1)
reject(2) // 错误调用
}
}
called 标志确保只调用一次 resolve/reject
五、手写实现总结
实现要点 | 说明 |
---|---|
状态管理 | 使用 state 属性跟踪状态 |
回调队列 | 收集 pending 状态的回调 |
异步执行 | 使用 setTimeout 模拟 |
链式调用 | 每个 then 返回新 Promise |
错误冒泡 | 自动传递错误到最近的 catch |
值穿透 | 处理非函数参数 |
六、与原生 Promise 的差异
特性 | 手写实现 | 原生 Promise |
---|---|---|
微任务队列 | 用宏任务模拟 | 真正的微任务 |
性能优化 | 未优化 | 高度优化 |
错误堆栈 | 简单追踪 | 完整错误追踪 |
特殊对象处理 | 基础实现 | 完整规范支持 |
七、最佳实践建议
-
生产环境:使用原生 Promise
-
学习目的:通过手写理解原理
-
扩展功能:
-
添加取消功能
-
实现进度通知
-
增加超时控制
-
通过这个手写实现,你已掌握:
-
Promise 的核心运行机制
-
异步任务队列管理
-
链式调用的实现原理
-
错误处理的最佳实践