开篇
Promise 是前端开发绕不开的基础概念,几乎每个异步场景都会用到它。
你可以把它理解成一句话:
Promise 是一个"将来会有结果"的承诺。
这个结果现在可能还没返回,所以它先处于等待(pending)状态;等异步任务结束后,要么成功(fulfilled),要么失败(rejected)。
- Promise 有哪几种状态?
Promise.all到底解决了什么问题?
带着这两个问题,我们一步步拆解。
一、Promise 的三个状态
Promise 一共三种状态,如下图所示:

text
pending 待处理(初始状态)
fulfilled 已成功(最终状态)
rejected 已失败(最终状态)
刚创建的 Promise 默认是 pending:
js
const p = new Promise(() => {});
console.log(p); // Promise { <pending> }
当异步任务成功时,它会变为 fulfilled:
js
const p = Promise.resolve("成功");
console.log(p); // Promise { <fulfilled>: "成功" }
当异步任务失败时,它会变为 rejected:
js
const p = Promise.reject("失败");
console.log(p); // Promise { <rejected>: "失败" }
这里有一条核心规则:
Promise 的状态一旦改变,就不可逆转。
也就是说,Promise 只能从 pending → fulfilled,或从 pending → rejected。一旦进入成功或失败状态,结果就已定格,无法再回到 pending,也不能从成功再变为失败。
二、为什么需要 Promise.all?
来看一个典型场景:页面需要同时展示一句随机名言和一张随机图片,两者来自不同的接口,互不依赖。
js
const getStory = async () =>
fetch("https://v1.hitokoto.cn/?c=i&encode=json");
const getRATP = async () =>
fetch("https://api.1314.cool/bingimg/?type=json&rand=1");
- 获取名言不需要等图片回来
- 获取图片也不需要等名言回来
既然二者没有依赖关系,就不应该串行等待,否则会浪费宝贵的网络时间。
三、串行写法:能用,但慢
如果写成这样:
js
async function main() {
const story = await getStory();
const ratp = await getRATP();
}
执行顺序是:
text
1. 发起 story 请求
2. 等待 story 返回
3. 再发起 ratp 请求
4. 等待 ratp 返回
这就是串行。假设每个接口耗时 1 秒,总耗时就是 2 秒。
但这两个请求明明可以同时进行,串行白白浪费了一半时间。
四、并发写法:Promise.all 闪亮登场
更好的方式是用 Promise.all 并发执行:
js
Promise.all([getStory(), getRATP()]);
这行代码的含义是:
同时发起所有异步任务,等它们全部成功后,再一起返回结果。
完整代码(采用链式写法):
js
const getStory = async () =>
fetch("https://v1.hitokoto.cn/?c=i&encode=json");
const getRATP = async () =>
fetch("https://api.1314.cool/bingimg/?type=json&rand=1");
async function main() {
Promise.all([getStory(), getRATP()])
.then((responses) => {
// responses 是 [Response, Response]
return Promise.all(responses.map((res) => res.json()));
})
.then((result) => {
console.log(result); // [名言数据, 图片数据]
})
.catch((error) => {
console.error("请求失败:", error);
});
}
main();
⚠ 注意大小写:
getRATP不要写成getRatp,JavaScript 是严格区分大小写的,这种小错误在面试手写题中很常见。
五、Promise.all 的结果顺序
Promise.all 有一个容易被忽视但很重要的特性:
返回结果的顺序,严格遵循传入数组的顺序,与每个 Promise 完成的先后无关。
例如:
js
Promise.all([getStory(), getRATP()])
.then(([storyResponse, imageResponse]) => {
console.log(storyResponse); // 始终是 getStory 的结果
console.log(imageResponse); // 始终是 getRATP 的结果
});
即使图片接口先返回,imageResponse 也一定是数组中的第二个元素。这种有序性在处理多个接口数据并需要对应关系时非常有用。
六、为什么还要再写一层 Promise.all?
细心的读者可能会疑惑:
明明已经用了
Promise.all,为什么还要再写一个Promise.all?
关键在于 fetch 的行为:
fetch返回的是一个Response对象,而不是最终的 JSON 数据。- 要读取响应体,需要调用
res.json(),而res.json()本身也返回一个 Promise。
所以,完整的流程其实是两层并发:

- 第一层
Promise.all:并发请求两个接口,拿到Response对象数组。 - 第二层
Promise.all:并发解析两个Response的 JSON 数据。
这种"嵌套 Promise.all"是处理多个 Fetch 请求的经典模式。
七、错误处理:一个失败,整体失败
Promise.all 的错误处理遵循"木桶短板"原则:
只要传入的 Promise 中有一个变为
rejected,Promise.all返回的 Promise 就会立即变为rejected,并把这个失败原因传递给catch。
js
Promise.all([p1, p2, p3])
.then((result) => {
console.log("全部成功", result);
})
.catch((error) => {
console.log("有一个失败了", error);
});
一旦 p1、p2、p3 中的任意一个失败,就会立即进入 catch。
但有一个容易混淆的点:
Promise.all变为 rejected,并不意味着其他已经开始的异步任务会被取消。
比如三个请求已经发出,第二个率先失败,那么 Promise.all 会立刻 reject,但第一个和第三个请求仍然会继续执行,只是它们的结果被忽略了。
如果在面试中被问到:
Promise.all 中一个 Promise 失败了,其他 Promise 还会执行吗?
可以这样回答:
如果其他 Promise 已经创建并开始执行,它们不会被自动取消,仍会完成。但
Promise.all会立即 reject,并将第一个失败原因返回给catch。
这个回答比"不会执行"更加准确。
补充 :如果你需要等待所有 Promise 完成(无论成功或失败),可以使用 Promise.allSettled,它会返回每个 Promise 的最终状态和结果,而不会提前 reject。
八、async/await 写法
上面的链式代码也可以改用 async/await 风格:
js
async function main() {
try {
const responses = await Promise.all([getStory(), getRATP()]);
const result = await Promise.all(
responses.map((res) => res.json())
);
console.log(result);
} catch (error) {
console.error("请求失败:", error);
}
}
两种写法本质相同:
text
.then().catch() ←→ async/await + try/catch
选择哪种取决于场景:
- 逻辑较短时,链式写法清晰。
- 流程较长或有复杂条件分支时,
async/await更易读。
九、面试怎么答?
如果面试官问:
Promise.all是什么?
可以这样回答:
Promise.all接收一个 Promise 数组,并发执行这些异步任务。只有当所有 Promise 都变为fulfilled时,它才会返回一个结果数组,顺序与传入数组严格一致。如果其中任何一个变为rejected,它会立即reject,并将该失败原因传递给catch。
如果追问:
一个失败了,其他 Promise 还会继续执行吗?
回答:
已经开始执行的 Promise 不会被自动取消,它们仍会运行至完成,只是
Promise.all已经进入rejected状态,不再等待和收集它们的结果。
这两个回答基本涵盖了 Promise.all 的核心考点。
十、总结
| 知识点 | 结论 |
|---|---|
| Promise 三状态 | pending、fulfilled、rejected |
| 状态变化规则 | 一旦变为成功或失败,就不可逆 |
Promise.all 用途 |
并发执行多个互不依赖的异步任务 |
| 成功结果 | 所有 Promise 成功后,返回结果数组,顺序与传入一致 |
| 失败规则 | 任意一个失败,整体立即失败,并返回第一个失败原因 |
| 是否取消其他任务 | 不会自动取消已开始的任务,它们仍会执行,但结果被忽略 |
| 替代方案 | 如需要所有结果(含失败),可使用 Promise.allSettled |
Promise 的难点不在于语法,而在于理解异步任务的执行顺序和并发控制。
Promise.all 最核心的价值就是:
把多个互不依赖的异步任务并发执行,并在全部成功后统一处理结果,从而大幅提升性能。