Promise 的 then 是如何实现链式调用的
Category(分类): JavaScript Status: 已整理(2026)
本文保留原文“
then返回新 Promise、回调队列、异步结果传递、多个then监听同一个 Promise、Promise/A+ 解析过程和手写实现”的主线。原文中的抓取格式标记、instanceof MyPromise识别 thenable、同步执行回调、失败处理和状态拼写已修正,并补充标准 Promise 与 Promise/A+ 的边界。
一、为什么 then 能链式调用
then 不会返回当前 Promise,而是立即返回一个新的 Promise。前一个 Promise 的处理函数执行后,其返回值会决定新 Promise 的状态:
- 返回普通值:新 Promise fulfilled,值就是返回值;
- 没有返回值:新 Promise fulfilled,值为
undefined; - 抛出异常:新 Promise rejected,原因是该异常;
- 返回 Promise 或 thenable:新 Promise 采用它最终的状态和值。
const result = Promise.resolve(1)
.then(value => value + 1)
.then(value => value * 2)
result.then(value => console.log(value)) // 4
这里每次 then 都返回下一环,而不是把同一个对象返回给自己:
const first = Promise.resolve(1)
const second = first.then(value => value + 1)
console.log(first === second) // false
二、异步返回值如何传到下一个 then
原文的示例等待两个定时器,最终约两秒后输出 2:
const result = Promise.resolve(1)
.then(value => {
return new Promise(resolve => {
setTimeout(() => resolve(value + 1), 1000)
})
})
.then(value => {
console.log(value) // 2,约 1 秒后
return value
})
如果第一个处理函数返回一个 pending Promise,第二个 then 不会立即收到 pending Promise,而是等它 fulfilled 或 rejected 后再继续:
Promise.resolve('start')
.then(value => new Promise(resolve => {
setTimeout(() => resolve(`${value} -> done`), 100)
}))
.then(console.log) // start -> done
这不是简单的“把回调放到下一个数组”就能完成的,还需要实现 Promise Resolution Procedure(Promise 解析过程),以处理普通值、Promise、thenable、异常和循环引用。

图片来源:原始图片。
三、回调队列和多个监听者
3.1 同一个 Promise 可以注册多个 then
const promise = new Promise(resolve => {
setTimeout(() => resolve(1), 100)
})
promise.then(value => console.log('first:', value))
promise.then(value => console.log('second:', value))
两个回调都注册在同一个 Promise 上,按照注册顺序执行;它们各自开启独立的后续链:
const source = Promise.resolve(1)
const chainA = source.then(value => value + 1)
const chainB = source.then(value => value + 10)
Promise.all([chainA, chainB]).then(console.log) // [2, 11]
chainA 不会等待 chainB,chainB 也不会等待 chainA。只有写在同一条链上的 then 才会依次等待上一个处理结果。

图片来源:原始图片。
3.2 同步注册,异步执行
then 的处理函数即使面对已经 fulfilled 的 Promise,也会异步执行:
const order = []
Promise.resolve().then(() => order.push('then'))
order.push('sync')
setTimeout(() => {
console.log(order) // ['sync', 'then']
}, 0)
标准 Promise 的处理函数会排入 Promise jobs/microtasks;浏览器和 Node.js 对宿主任务的调度细节不同,但不能把 then 回调当作当前调用栈中的同步函数。
四、处理函数不是函数时的值穿透
then 的两个参数都可省略。如果 onFulfilled 不是函数,默认使用 identity 函数;如果 onRejected 不是函数,默认使用 thrower 函数:
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(value => console.log(value)) // 1
错误会继续向后传递,直到遇到真正的拒绝处理函数:
Promise.reject(new Error('failed'))
.then(2, 3)
.catch(error => console.log(error.message)) // failed
这也是 catch(onRejected) 可以实现为 then(undefined, onRejected) 的原因:
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected)
}
原生 Promise 的 catch 已由运行时提供,不要覆盖内置原型;上面的代码只用于说明关系。
五、Promise Resolution Procedure
可以把新 Promise 的解析过程简化成以下规则:
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Promise 自引用'))
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called = false
try {
const then = x.then
if (typeof then === 'function') {
then.call(
x,
value => {
if (called) return
called = true
resolvePromise(promise2, value, resolve, reject)
},
reason => {
if (called) return
called = true
reject(reason)
}
)
} else {
resolve(x)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
return
}
resolve(x)
}
这段代码展示的是核心思想,不是完整的原生 Promise 实现:
- 不能让新 Promise 解析为自己;
- 只要返回值是对象或函数,就读取一次
then; then是函数时,把它当作 thenable 调用;- thenable 只能以第一次
resolve/reject为准; - thenable 的成功值还要递归解析;
- 读取
then、调用then或处理函数抛错时要拒绝新 Promise; - 普通值直接 fulfilled。
不能只用 x instanceof MyPromise 判断,因为其他 Realm 的 Promise、原生 Promise 和用户自定义 thenable 都可能不是当前类的实例。Promise 互操作依靠的是 thenable 协议。
六、一个可运行的最小 Promise 实现
下面的 TinyPromise 用 queueMicrotask 模拟标准 Promise 的异步回调,用构造器的 resolve 逻辑实现 thenable 采纳。它用于学习,不是生产级 Promise polyfill,也没有覆盖所有宿主和规范边界。
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class TinyPromise {
constructor(executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
this.handlers = []
let alreadyResolved = false
const fulfill = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
this.flush()
}
const rejectInternal = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
this.flush()
}
const resolveValue = value => {
if (value === this) {
rejectInternal(new TypeError('Promise cannot resolve to itself'))
return
}
if (value !== null &&
(typeof value === 'object' || typeof value === 'function')) {
let then
try {
then = value.then
} catch (error) {
rejectInternal(error)
return
}
if (typeof then === 'function') {
let called = false
try {
then.call(
value,
nextValue => {
if (called) return
called = true
resolveValue(nextValue)
},
reason => {
if (called) return
called = true
rejectInternal(reason)
}
)
} catch (error) {
if (!called) {
called = true
rejectInternal(error)
}
}
return
}
}
fulfill(value)
}
const resolve = value => {
if (alreadyResolved) return
alreadyResolved = true
resolveValue(value)
}
const reject = reason => {
if (alreadyResolved) return
alreadyResolved = true
rejectInternal(reason)
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
flush() {
const handlers = this.handlers.splice(0)
for (const handler of handlers) {
queueMicrotask(handler)
}
}
then(onFulfilled, onRejected) {
const fulfilled = typeof onFulfilled === 'function'
? onFulfilled
: value => value
const rejected = typeof onRejected === 'function'
? onRejected
: reason => { throw reason }
let resolveNext
let rejectNext
const nextPromise = new TinyPromise((resolve, reject) => {
resolveNext = resolve
rejectNext = reject
})
const run = () => {
try {
const callback = this.status === FULFILLED ? fulfilled : rejected
const input = this.status === FULFILLED ? this.value : this.reason
resolveNext(callback(input))
} catch (error) {
rejectNext(error)
}
}
if (this.status === PENDING) {
this.handlers.push(run)
} else {
queueMicrotask(run)
}
return nextPromise
}
catch(onRejected) {
return this.then(undefined, onRejected)
}
finally(onFinally) {
if (typeof onFinally !== 'function') return this.then()
return this.then(
value => TinyPromise.resolve(onFinally()).then(() => value),
reason => TinyPromise.resolve(onFinally()).then(() => {
throw reason
})
)
}
static resolve(value) {
return value instanceof TinyPromise
? value
: new TinyPromise(resolve => resolve(value))
}
static reject(reason) {
return new TinyPromise((resolve, reject) => reject(reason))
}
static all(iterable) {
const values = Array.from(iterable)
return new TinyPromise((resolve, reject) => {
if (values.length === 0) {
resolve([])
return
}
const result = new Array(values.length)
let remaining = values.length
values.forEach((value, index) => {
TinyPromise.resolve(value).then(item => {
result[index] = item
remaining -= 1
if (remaining === 0) resolve(result)
}, reject)
})
})
}
static race(iterable) {
return new TinyPromise((resolve, reject) => {
for (const value of iterable) {
TinyPromise.resolve(value).then(resolve, reject)
}
})
}
}
6.1 使用最小实现
const result = new TinyPromise(resolve => {
setTimeout(() => resolve(1), 10)
})
.then(value => value + 1)
.then(value => console.log(value)) // 2
TinyPromise.all([TinyPromise.resolve(1), 2])
.then(values => console.log(values)) // [1, 2]
这里有几个容易漏掉的点:
- 构造器执行器是同步调用的,但
then处理函数异步执行; resolve不能只设置 fulfilled,因为它还需要采纳 thenable;resolve/reject的第一次调用胜出;then返回的新 Promise 需要拿到处理函数的结果;- 处理函数返回的新 Promise 也需要被解析;
- 空数组的
all([])应立即 fulfilled 为[]; - 空 iterable 的
race([])保持 pending; finally不改变原来的值或拒绝原因,除非它自身抛错或返回 rejected Promise。
七、原文的链式调用示例(现代写法)
const start = Date.now()
const chain = new TinyPromise(resolve => {
console.log('executor') // 同步执行
setTimeout(() => resolve(1), 1000)
})
.then(value => new TinyPromise(resolve => {
setTimeout(() => resolve(value + 1), 1000)
}))
.then(value => {
console.log(value, Date.now() - start) // 2,约 2000ms
return value
})
chain.catch(console.error)
如果在同一个源 Promise 上注册两个监听:
const source = new TinyPromise(resolve => {
setTimeout(() => resolve(1), 1000)
})
source.then(value => {
console.log('chain A:', value)
return new TinyPromise(resolve => {
setTimeout(() => resolve(value + 1), 1000)
})
}).then(value => console.log('chain A result:', value))
source.then(value => {
console.log('chain B:', value)
})
chain B 会在源 Promise 完成后很快执行,并不会等待 chain A 返回的第二个 Promise;它们是两条独立的链。
八、Promise/A+ 与 ECMAScript Promise 的关系
Promises/A+ 主要规定 then 的行为和 Promise Resolution Procedure,关注不同 Promise 实现之间的互操作;它并不完整规定构造器、catch、finally、all、race、微任务队列或浏览器/Node.js 宿主行为。
ECMAScript 原生 Promise 还规定:
- Promise 构造器和 resolving functions;
then、catch、finally;resolve、reject、all、allSettled、any、race、withResolvers等 API;- Promise jobs 的异步调度;
- 与 async/await、模块和宿主 API 的集成。
因此,通过 Promise/A+ 测试并不等于实现了完整的原生 Promise;反过来,学习 thenable 解析仍然是理解链式调用的核心。
九、常见错误
9.1 只判断 instanceof
function isPromiseLike(value) {
return value !== null &&
(typeof value === 'object' || typeof value === 'function') &&
typeof value.then === 'function'
}
读取 then 可能抛错或触发 getter,真实解析过程必须放进 try/catch,并且只读取一次。
9.2 用 return 返回错误对象
返回 Error 对象是 fulfilled,不是 rejected:
Promise.resolve()
.then(() => new Error('普通返回值'))
.then(value => console.log(value instanceof Error)) // true
Promise.resolve()
.then(() => { throw new Error('抛出错误') })
.catch(error => console.log(error.message)) // 抛出错误
9.3 把多个 then 当成同一条链
const source = Promise.resolve(1)
const first = source.then(value => value + 1)
const second = source.then(value => value + 2)
Promise.all([first, second]).then(console.log) // [2, 3]
要让后一个步骤等待前一个步骤,必须把它写在前一个 then 返回的 Promise 上:
Promise.resolve(1)
.then(value => value + 1)
.then(value => value + 2)
.then(console.log) // 4
十、总结
then每次返回新的 Promise,这是链式调用的根本;- 处理函数返回普通值、抛出异常或返回 thenable,会分别影响新 Promise 的状态;
- 非函数处理器实现值穿透和错误继续传递;
- 同一个 Promise 可以注册多个回调,回调按注册顺序执行,但每个
then开启独立链; - 标准 Promise 的处理函数异步执行,即使源 Promise 已经 settled;
- Promise 解析过程必须处理 thenable、一次性调用、异常和自引用;
instanceof MyPromise不能代替 thenable 解析;- Promise/A+ 主要规定
then互操作,不等于完整 ECMAScript Promise; - 手写实现适合学习,生产代码应使用原生 Promise 或经过验证的库。