创见博客
多个并发请求,如何按顺序依次输出结果?
七崽爱吃小饼干2026/09/23阅读 0

这是一个非常典型的面试题,也是真实业务里绕不开的场景:页面同时发起 5 个请求,但结果必须按 1、2、3、4、5 的顺序展示。

难点在于"并发"和"有序"天生矛盾:并发追求快的先返回,有序要求慢的也得等前面。这篇文章给出三种常见实现,并分析各自的取舍。

一、先明确问题

假设有下面这组异步任务(task() 调用即发起请求,返回 Promise):

js
const tasks = [
  () => delay(300).then(() => '结果 1'),
  () => delay(100).then(() => '结果 2'),
  () => delay(200).then(() => '结果 3'),
];

function delay(ms) {
  return new Promise((resolve) => setTimeout(() => resolve(ms), ms));
}

如果直接 Promise.all(tasks.map((fn) => fn())),结果数组顺序是对的,但输出时机是并发的——谁先返回谁先处理。而题目要求的是:三个请求同时发出,但打印顺序必须是 1、2、3。

所以核心是:并发执行,但按原顺序消费(或输出)结果。

二、方案一:Promise.all 后按顺序输出

最简单的方式:等所有请求都完成后,再按数组顺序逐条输出。

js
const results = await Promise.all(tasks.map((fn) => fn()));
results.forEach((r) => console.log(r)); // 结果 1、结果 2、结果 3

优点:代码最短,顺序绝对正确。

缺点:必须等最慢的那个返回,才能开始输出,实时性差。如果第一个请求早就回来了,用户却要等第三个请求完成才看到第一条,体验不好。

三、方案二:async/await 逐个等待(串行)

用 for...of 配合 await,在循环里调用任务:

js
for (const fn of tasks) {
  console.log(await fn()); // 逐个等待,顺序输出
}

输出顺序正确,第一个结果一到就能打印,实时性好。但要注意:这是串行执行——第二个请求要等第一个完成后才发起,总耗时是三者之和。只有任务之间真的存在依赖(比如后一个要用前一个的 token)时才适合。

四、方案三:先并发启动,再按序 await(推荐)

如果想要"并发发出 + 按顺序输出 + 实时打印",可以先把所有任务启动,拿到一组 Promise,再在循环里按顺序 await:

js
// 第一步:map 是同步的,循环里立即调用 task(),请求全部并发发出
const pendingPromises = tasks.map((task) => task());

// 第二步:按原数组顺序逐个 await,输出自然有序
const printInOrder = async (promises) => {
  for (const promise of promises) {
    console.log(await promise); // 注意是 await promise,不是 await promise()
  }
};

printInOrder(pendingPromises);

为什么它能同时满足"并发"和"有序":

  • tasks.map((task) => task()) 在同步循环中立即调用每个任务,所有请求同时发出,实现了并发。
  • for...of 里的 await 按数组原顺序逐个等待,所以输出顺序固定。
  • await 对已经 resolve 的 Promise 会立即返回,所以第一个结果一就绪就能立刻打印,不必等最慢的那个。

一个小坑:map 之后数组里已经是 Promise,不是函数了,循环里必须写 await promise。如果手滑写成 await promise(),会报 TypeError: promise is not a function。

补一个错误处理的写法

如果后面的任务先 reject,而它还没轮到被 await,会触发 UnhandledPromiseRejection。稳妥做法是先把每个 Promise 兜底成结果对象,再按序 await:

js
const pendingPromises = tasks.map((task) =>
  task().then(
    (value) => ({ ok: true, value }),
    (error) => ({ ok: false, error })
  )
);

for (const promise of pendingPromises) {
  const res = await promise;
  if (!res.ok) throw res.error;
  console.log(res.value);
}

五、三种方案怎么选?

方案是否并发是否实时输出适用场景
Promise.all 后按顺序输出是否,要等最慢结果一次性渲染,不在乎实时性
async/await 逐个等待否(串行)是任务之间有依赖,必须串行
先并发启动再按序 await是是推荐,并发且保序

一句话总结:想要并发又保序,就先把任务 map 成一组 Promise 全部启动,再按原顺序逐个 await。

六、几个容易踩的坑

  • map 之后拿到的是一组 Promise,循环里是 await promise,不是 await promise()。
  • await 写在 forEach 里不会生效,因为 forEach 不接受 async 回调的返回值。要顺序执行用 for...of。
  • 用 Promise.all 得到的数组顺序是参数顺序,不是返回顺序,但别把"数组有序"和"输出有序"混为一谈——map 里如果直接 .then(console.log),输出就是乱序的。
  • 写 const tasks = tasks.map(...) 复用同名变量容易踩 TDZ(暂时性死区)错误,建议换个变量名,比如 pendingPromises。
评论
0/100