重点总结
定义:Promise 是异步编程的一种解决方案,解决了回调地狱问题,提供了统一的 API。
状态:它有 Pending、Fulfilled、Rejected 三种状态,且状态不可逆。
使用:
构造函数是同步执行的。
.then 返回新 Promise,支持链式调用。
.catch 处理错误(冒泡机制)。
常用 API:
all(并发,全对才对)。
race(赛跑,谁快用谁)。
allSettled(不管死活,都要结果)。
any(只要有一个活的就行)。
运行机制 :Promise 的回调是微任务,在同步代码执行完后、宏任务执行前执行,这涉及到了 Event Loop。
最佳实践:现在项目中主要配合 async/await 使用,使异步代码看起来像同步代码,可读性更强。
1. 什么是 Promise?(核心概念)
一句话定义: Promise 是异步编程的一种解决方案,它是一个对象,代表了一个异步操作的最终完成(或失败)及其结果值。
1.1 为什么要用 Promise?
在 Promise 出现之前,我们处理异步主要靠回调函数(Callback)。当异步操作依赖另一个异步操作时,就会出现"回调地狱"(Callback Hell):
javascript
// 噩梦般的回调地狱
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
Promise 的作用: 将嵌套的回调改为链式调用,解决了回调地狱问题,使代码更具可读性和逻辑性,同时提供了更好的错误处理机制。
1.2 Promise 的三种状态
Promise 是一个状态机,它拥有三种状态:
-
Pending(进行中):初始状态,既没有被兑现,也没有被拒绝。
-
Fulfilled(已成功/Resolved):意味着操作成功完成。
-
Rejected(已失败):意味着操作失败。
重要特性:状态不可逆
一旦状态从 Pending 变为 Fulfilled 或 Rejected,状态就固定了,不会再发生改变。这被称为 "Settled"(已决议)。
2. Promise 的基本使用与实例方法
2.1 构造函数 (Executor)
javascript
const p = new Promise((resolve, reject) => {
// Executor 执行器函数,是【同步执行】的!
console.log('1. Executor runs immediately');
// 模拟异步
setTimeout(() => {
const success = true;
if (success) {
resolve('Success Data'); // 状态变成 Fulfilled
} else {
reject('Error Reason'); // 状态变成 Rejected
}
}, 1000);
});
面试考点: new Promise(fn) 中的 fn 是立即同步执行的。
2.2 .then(onFulfilled, onRejected)
-
作用:接收 Promise 成功或失败的结果。
-
返回值 :返回一个新的 Promise(这是链式调用的关键)。
-
特性:
-
如果回调函数返回一个普通值,新 Promise 状态为 Fulfilled,值为该返回值。
-
如果回调函数抛出错误,新 Promise 状态为 Rejected。
-
如果回调函数返回一个 Promise,新 Promise 将等待这个 Promise 的状态。
-
值穿透:如果 .then() 没传回调函数,值会原样向后传递。
-
2.3 .catch(onRejected)
-
作用:捕获 Promise 链中的错误。
-
本质:它只是 .then(null, onRejected) 的语法糖。
-
冒泡性:错误会沿着链一直向下传递,直到被最近的 catch 捕获。
2.4 .finally(onFinally)
-
作用:无论 Promise 是成功还是失败,都会执行。
-
场景:关闭 Loading 动画、关闭数据库连接等清理工作。
-
注意:finally 不接收任何参数,它也不知道前面的状态是啥。它返回的 Promise 通常会延续上一个 Promise 的结果(除非 finally 里面抛错)。
3. Promise 静态方法 (API 详解)
这是面试中最常考察应用场景的部分。
3.1 Promise.resolve(value)
-
作用:将现有对象转为 Promise 对象(状态为 Fulfilled)。
-
场景:当你需要一个确定的成功结果进行后续链式调用时。
3.2 Promise.reject(reason)
- 作用:返回一个状态为 Rejected 的 Promise 实例。
3.3 Promise.all([p1, p2, p3])
-
逻辑 :"并发执行,全对才对,一错全错"。
-
输入:一个 Promise 数组(Iterator)。
-
输出:
-
成功:当所有 Promise 都成功时,返回一个数组,包含所有结果(顺序与输入一致)。
-
失败:只要有一个失败,立即返回那个失败的原因(短路效应)。
-
-
场景:页面加载时,同时请求用户信息、菜单列表、系统配置,三个都拿到才能渲染页面。
3.4 Promise.race([p1, p2, p3])
-
逻辑 :"赛跑机制,谁快听谁的"。
-
输出:返回第一个改变状态(无论是成功还是失败)的 Promise 的结果。
-
场景:
- 请求超时控制:比如网络请求和 setTimeout 赛跑,如果 5秒没回来,setTimeout 赢了,抛出超时错误。
3.5 Promise.allSettled([p1, p2, p3]) (ES2020)
-
逻辑 :"我全都要,不管对错"。
-
输出:等待所有 Promise 都结束(Settled)。返回一个数组,每个对象包含 status ('fulfilled'/'rejected') 和对应的 value 或 reason。
-
场景:你需要知道所有请求的结果,不关心其中某个是否失败。例如批量上传图片,失败几张没关系,需要知道哪几张成功哪几张失败。
3.6 Promise.any([p1, p2, p3]) (ES2021)
-
逻辑 :"只要有一个成就算成功"。
-
输出:
-
只要有一个 Promise 成功,就返回那个成功的。
-
只有当所有 Promise 都失败时,才返回失败(AggregateError)。
-
-
场景:从多个 CDN 节点加载同一张图片,只要有一个加载出来就行。
4. 进阶:Promise 与事件循环 (Event Loop)
这是区分初级和中高级开发者的分水岭。
4.1 微任务 (Microtask)
Promise 的 .then、.catch、.finally 中的回调函数属于 微任务 。
setTimeout、setInterval 属于 宏任务 (Macrotask)。
4.2 执行顺序
-
执行同步代码(包括 new Promise 构造函数内部的代码)。
-
同步代码执行完,检查并执行所有微任务队列(Promise 回调)。
-
渲染 DOM(如果有必要)。
-
执行一个宏任务。
-
回到步骤 2 循环。
经典面试题:
javascript
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
new Promise((resolve) => {
console.log('4');
resolve();
}).then(() => {
console.log('5');
});
console.log('6');
答案与解析:
输出顺序:1 -> 4 -> 6 -> 3 -> 5 -> 2
-
1: 同步。
-
setTimeout: 放入宏任务队列。
-
3: 放入微任务队列。
-
4: new Promise 是同步执行的。
-
5: 放入微任务队列。
-
6: 同步。
-
同步结束,清空微任务:输出 3, 5。
-
微任务清空,执行宏任务:输出 2。
5. 面试常见"手写"题思路
面试官可能会让你手写 Promise.all 或简易版 Promise。
5.1 手写 Promise.all
核心逻辑:
-
返回一个新的 Promise。
-
遍历输入数组。
-
用 Promise.resolve 包装每一项(防止数组里有非 Promise 值)。
-
维护一个计数器 count 和结果数组 results。
-
每成功一个,results[i] = value,count++。
-
如果 count === length,调用 resolve(results)。
-
只要有一个失败,直接调用 reject(reason)。
javascript
Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
let results = [];
let count = 0;
if(promises.length === 0) resolve([]);
promises.forEach((p, index) => {
Promise.resolve(p).then(res => {
results[index] = res; // 保证结果顺序和输入顺序一致
count++;
if (count === promises.length) {
resolve(results);
}
}, err => {
reject(err);
})
})
})
}
6. Promise 的终极形态:Async/Await
面试必问:Async/Await 和 Promise 的关系?
-
async/await 是 Generator + Promise 的语法糖。
-
async 函数返回的一定是一个 Promise。
-
await 后面通常接 Promise,它会暂停 async 函数的执行,等待 Promise 解决,并把结果作为 await 表达式的值。
-
优势:用同步的代码逻辑写异步代码,彻底解决了 .then 链过长的问题,try...catch 可以同时捕获同步和异步错误。