跳到主要内容

Promise 使用与原理:then 链、微任务与组合方法(面试向)

Promise 不是“让代码变快”的魔法,它解决的是:把一次异步操作的最终结果(成功值/失败原因)抽象成一个可组合的对象,从而让你可以用链式写法、错误传播与并发组合,把异步逻辑写得更可控、更可推理。


面试速答(30 秒 TL;DR)

  • Promise 表示“未来某个时刻才会得到的单次结果”,内部是三态状态机pendingfulfilledrejected只会变化一次)。
  • new Promise(executor) 里的 executor 同步执行;但 then/catch/finally 的回调 一定异步,以**微任务(microtask)**方式调度。
  • then 永远返回一个新的 Promise:回调 return 什么,新 Promise 就“跟着变成什么”(返回普通值→成功;throw→失败;返回 Promise/thenable→同化其状态)。
  • 组合方法:
    • Promise.all:全成功才成功,任一失败就失败(失败快)
    • Promise.allSettled:全收集,不会短路
    • Promise.race:第一个“落定”(成功或失败)就结束
    • Promise.any:第一个成功就成功;全失败抛 AggregateError
  • Promise 不是线程;“并发”来自你同时发起了多少 I/O(请求/读写/定时器),而不是 Promise 自己并行计算。

1. Promise 是什么:把“未来的结果”变成对象

你可以把 Promise 理解为一个带状态的盒子:

  • 盒子一开始是 pending(未完成)
  • 未来某一刻变成 fulfilled(value)rejected(reason)
  • 你可以提前在盒子上“登记回调”(then/catch/finally),等它落定后自动按规则执行
术语对齐(很容易混)
  • resolve:把“结果”交给 Promise 处理(它可能是普通值,也可能是另一个 Promise/thenable)。
  • fulfill:Promise 最终以“成功值”落定。
  • reject:Promise 最终以“失败原因”落定。

2. 基本用法:then/catch/finallyasync/await

2.1 then():拿到成功值并继续链式处理

fetch('/api/user')
.then((res) => res.json())
.then((user) => {
console.log('user:', user);
return user.id;
})
.then((id) => fetch(`/api/user/${id}/profile`))
.then((res) => res.json())
.then((profile) => console.log('profile:', profile));

2.2 catch():统一处理链上的错误

catch(onRejected) 等价于 then(undefined, onRejected)

doSomething()
.then(() => doNext())
.catch((err) => {
console.error('出错了:', err);
});

2.3 finally():无论成功失败都做收尾(不会“吃掉”结果)

finally 的回调不接收 value/reason;它的典型用途是“关 loading / 释放资源 / 打点”。

loadData()
.finally(() => setLoading(false))
.then(render)
.catch(showError);

关键规则(面试高频):

  • finally(() => 123) 返回值会被忽略,不会替换原来的 value/reason
  • 但如果 finally 抛错或返回一个失败的 Promise,会让链变成失败
Promise.resolve('OK')
.finally(() => Promise.reject('boom'))
.then(console.log)
.catch(console.log); // boom

2.4 async/await:Promise 的语法糖

async function getUser() {
const res = await fetch('/api/user');
return res.json();
}

getUser().then(console.log);

语义映射:

  • async function 一定返回 Promise
  • return x 等价于 return Promise.resolve(x)
  • throw e 等价于 return Promise.reject(e)

3. 核心原理:为什么 then 能“接着写”

3.1 executor 同步,回调微任务:执行顺序题的核心

console.log('A');

new Promise((resolve) => {
console.log('B');
resolve('C');
}).then((v) => console.log(v));

console.log('D');

输出顺序是:

A
B
D
C

原因:

  • executor 立即执行,所以先打印 B
  • then 回调进入微任务队列,要等当前调用栈清空后才执行

如果你对“微任务/宏任务/事件循环”想系统补齐,可以看:docs/frontend/javascript/event-loop.md

3.2 then 为什么会返回“新的 Promise”

then 的本质不是“在原 Promise 上继续改”,而是:基于旧 Promise 的结果,创建一个新 Promise(链条上的下一环)

3.3 Promise Resolution Procedure:同化 thenable(原理的“最硬核”部分)

面试经常问:为什么 return 一个 Promise,链就会“等它”

因为 then 在内部会对回调的返回值 x 做一套“吸收/同化”流程(简化描述):

你只需要记住这句口述版:

then 返回的新 Promise(P2),会根据回调返回值来决定自己怎么落定:
返回值是普通值就成功;抛错就失败;返回 Promise/thenable 就“跟着它走”。


4. 组合方法:all/allSettled/race/any 怎么选

API何时 fulfilled何时 rejected典型用途
Promise.all(list)全部成功,值为结果数组任意一个失败(失败快)并行请求,要求“缺一不可”
Promise.allSettled(list)永远成功,值为状态数组仅当传参非法等异常需要“收集全部结果(含失败)”
Promise.race(list)第一个落定是成功第一个落定是失败超时、竞速、取最快响应
Promise.any(list)任意一个成功全部失败(AggregateError只要有一个可用就行(多源兜底)

4.1 Promise.all:并行 + 失败快

const [user, profile] = await Promise.all([getUser(), getProfile()]);

4.2 Promise.race:实现超时

function timeout(ms) {
return new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), ms));
}

await Promise.race([fetch('/api/data'), timeout(3000)]);
注意

race 只决定“谁先落定”;落定之后,其他 Promise 并不会自动取消。需要“真的取消”,请用 AbortController(如果底层 API 支持)。


5. 典型题 & 标准答法(带口述要点)

Q1:new Promise 里的代码是同步还是异步?

答法: executor 同步执行;then/catch/finally 回调异步执行(微任务)。

Q2:then 链里 return 和不 return 有啥区别?

答法: then 返回的新 Promise 要靠你的 return 来接住下一步;不 return 等价于返回 undefined,下一步拿到的就是 undefined,并且不会等待你在回调里“偷偷开启”的异步操作。

Q3:Promise.all vs allSettled vs any 怎么选?

答法:

  • 全成功才行:all
  • 全部都要统计(不管成功失败):allSettled
  • 只要一个成功即可(多源兜底):any

Q4:为什么 try/catch 抓不到 Promise 链里的错误?

答法: 因为 Promise 回调在微任务里执行,已经不在当前同步调用栈;要用 .catch() 或在 async/await 里用 try/catch 包住 await


6. 常见追问

6.1 Promise 能取消吗?

Promise 本身不能取消。 常见做法:

  • 通过 AbortController 取消底层操作(如 fetch
  • 自己做“忽略结果”或“竞速超时”封装(逻辑取消,而非底层取消)

6.2 浏览器为什么会报 “Unhandled Promise Rejection”?

当一个 Promise 被 reject 且在“合适的时机”还没有挂上拒绝处理器(catchthen 的第二参),宿主环境会触发未处理拒绝的告警/事件。工程里建议:所有链都要有兜底 catch,或在更上层统一捕获与上报。


7. 易错点 / 坑(高频踩雷清单)

  • Promise 构造器反模式:能用现成 Promise/async 就别 new Promise 再包一层(容易漏 resolve/reject、吞错)。
  • forEach + asyncforEach 不会等 await,要用 for...ofPromise.all(map(...))
  • 忘了 returnthen(() => fetch(...)) 必须 return fetch(...) 才能让后续等待它。
  • finally 误解finally 不会拿到 value/reason;返回值默认不会替换链上的结果。
  • race 当取消用race 不等于取消;超时后请求可能仍在进行,需要 AbortController 配合。

8. 速记要点(可背诵)

  • 三态:pendingfulfilled / rejected,只变一次
  • executor 同步;then/catch/finally 微任务
  • then 产出新 Promise:return 值/抛错/返回 Promise 决定下一环
  • all 缺一不可;allSettled 全收集;race 取第一个落定;any 取第一个成功