Promise 几乎天天在用,但真要你手写一个,很多人就卡住了。其实它的核心机制并不神秘:一个状态机 + 一组回调队列 + 链式调用。
这篇我们从零实现一个符合 Promise/A+ 核心语义的版本,把它内部到底怎么运转讲清楚。看完你会对 then 链、值穿透、错误冒泡有完全不同的理解。
先明确 Promise 的三个规则
实现之前,先把规则定下来,这是后面所有代码的依据:
- 三种状态:
pending(进行中)、fulfilled(已成功)、rejected(已失败)。 - 状态只能改变一次:
pending → fulfilled或pending → rejected,一旦改变就锁定,不可逆。 then接收两个回调:onFulfilled和onRejected,状态变化时执行对应的那个。
第一版:基础状态机
先实现状态流转。构造函数接收一个 executor,立即执行,并给它传 resolve 和 reject 两个函数:
class MyPromise {
constructor(executor) {
this.state = 'pending'
this.value = undefined // 成功的值
this.reason = undefined // 失败的原因
const resolve = (value) => {
if (this.state !== 'pending') return // 状态只能改一次
this.state = 'fulfilled'
this.value = value
}
const reject = (reason) => {
if (this.state !== 'pending') return
this.state = 'rejected'
this.reason = reason
}
try {
executor(resolve, reject) // executor 同步执行
} catch (err) {
reject(err) // executor 里抛错,直接 reject
}
}
}这一版能记录状态和值,但还没法注册回调。
第二版:支持 then(含异步)
then 要根据当前状态执行对应回调。难点在于:resolve 可能是异步调用的(比如在 setTimeout 里),这时候调用 then,状态还是 pending,回调没法立刻执行。
解决办法:pending 时把回调先存起来,等 resolve / reject 时再拿出来执行。这就是"回调队列"。
class MyPromise {
constructor(executor) {
this.state = 'pending'
this.value = undefined
this.reason = undefined
this.onFulfilledCbs = [] // 成功回调队列
this.onRejectedCbs = [] // 失败回调队列
const resolve = (value) => {
if (this.state !== 'pending') return
this.state = 'fulfilled'
this.value = value
this.onFulfilledCbs.forEach((fn) => fn()) // 状态确定,把存好的回调都执行
}
const reject = (reason) => {
if (this.state !== 'pending') return
this.state = 'rejected'
this.reason = reason
this.onRejectedCbs.forEach((fn) => fn())
}
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value)
} else if (this.state === 'rejected') {
onRejected(this.reason)
} else {
// pending:先存起来,等状态确定再执行
this.onFulfilledCbs.push(() => onFulfilled(this.value))
this.onRejectedCbs.push(() => onRejected(this.reason))
}
}
}现在它已经能处理异步了:
new MyPromise((resolve) => {
setTimeout(() => resolve('ok'), 1000)
}).then((v) => console.log(v)) // 1 秒后打印 ok第三版:链式调用与值穿透
这是 Promise 最精髓、也最难的部分。then 要返回一个新的 Promise,才能链式调用,而且要处理几种情况:
- 回调返回普通值 → 新 Promise 用这个值 resolve。
- 回调返回一个 Promise → 新 Promise 要等它,跟随它的结果。
- 回调抛错 → 新 Promise reject。
- 回调没传(值穿透) → 把值/原因往后传。
then(onFulfilled, onRejected) {
// 值穿透:onFulfilled / onRejected 不是函数时,给个默认实现把值往后传
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v
onRejected = typeof onRejected === 'function' ? onRejected : (e) => { throw e }
// then 返回新 Promise,这是链式调用的关键
const promise2 = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
// 用 setTimeout 模拟微任务,保证回调异步执行(Promise/A+ 要求)
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject) // 处理返回值
} catch (err) {
reject(err)
}
})
}
const handleRejected = () => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
if (this.state === 'fulfilled') handleFulfilled()
else if (this.state === 'rejected') handleRejected()
else {
this.onFulfilledCbs.push(handleFulfilled)
this.onRejectedCbs.push(handleRejected)
}
})
return promise2
}核心是那个 resolvePromise,它负责处理"回调返回值 x":如果 x 是个 Promise(或 thenable),就跟随它;否则直接 resolve。
function resolvePromise(promise2, x, resolve, reject) {
// 防止循环引用:then 返回了自己
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'))
}
// x 是对象或函数,可能是 thenable
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called = false // 防止 resolve / reject 被重复调用
try {
const then = x.then
if (typeof then === 'function') {
// x 是 thenable,跟随它的状态
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) // 普通对象,直接 resolve
}
} catch (err) {
if (called) return
called = true
reject(err)
}
} else {
resolve(x) // x 是普通值
}
}到这里,核心的链式调用就完成了:
new MyPromise((resolve) => resolve(1))
.then((v) => v + 1) // 返回普通值
.then((v) => new MyPromise((r) => r(v * 10))) // 返回 Promise
.then((v) => console.log(v)) // 20值穿透也能用了:
new MyPromise((resolve) => resolve('hi'))
.then() // 没传回调,值穿透
.then((v) => console.log(v)) // 'hi'补上 catch、resolve、reject、all
有了 then,其它方法都是它的语法糖或简单封装。
catch 就是只传第二个参数的 then:
catch(onRejected) {
return this.then(null, onRejected)
}静态的 resolve / reject:
static resolve(value) {
if (value instanceof MyPromise) return value
return new MyPromise((resolve) => resolve(value))
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason))
}Promise.all:全部成功才成功,按顺序收集结果,任一失败则整体失败:
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = []
let count = 0
promises.forEach((p, index) => {
// 用 MyPromise.resolve 包一层,兼容传入非 Promise 值
MyPromise.resolve(p).then(
(value) => {
results[index] = value // 按原顺序放,不是按完成顺序
count++
if (count === promises.length) resolve(results)
},
(reason) => reject(reason), // 一个失败,整体失败
)
})
if (promises.length === 0) resolve([])
})
}Promise.race:谁先有结果就用谁的:
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach((p) => {
MyPromise.resolve(p).then(resolve, reject) // 第一个改变状态的说了算
})
})
}几个关键点回顾
手写过一遍,这几个机制就真正理解了:
- 状态不可逆:
resolve/reject开头那句if (this.state !== 'pending') return,保证状态只变一次。 - 回调队列:
pending时回调存进数组,状态确定时统一触发——这是支持异步的关键。 then返回新 Promise:链式调用的根本,每个then产生一个新的 Promise。resolvePromise:处理回调返回值,返回 Promise 就跟随它,这是链式能"等待"的原因。- 值穿透:
then不传回调时,用默认函数把值/错误往后传,所以.then().then(fn)也能拿到值。 - 错误冒泡:某一环 reject 或抛错,会一路往后传,直到被某个
onRejected/catch捕获。
说明:真实 Promise 的回调是放进微任务队列的,这里用
setTimeout模拟成宏任务只是为了演示"异步执行"这个语义。要严格符合微任务时序,可以用queueMicrotask替换setTimeout。
总结
Promise 看似复杂,拆开看就是三块:
- 一个状态机:三状态、只能变一次。
- 两个回调队列:解决异步注册的回调延后执行。
then返回新 Promise +resolvePromise:撑起链式调用、值穿透和错误冒泡。
把这套亲手写一遍,你对 async/await(它本质是 Promise 的语法糖)、对事件循环里的微任务、对各种链式调用的执行顺序,都会有质的理解。这也是为什么"手写 Promise"一直是检验 JS 功底的经典题目。