先记住这个答案
Promise.all 采用快速失败策略:只要输入数组中的任何一个 Promise 转为 rejected,返回的 Promise 就立即以相同的 rejection reason 拒绝,而无须等待其他 Promise。其他未决的 Promise 并不会被取消或停止,它们会继续执行自己的异步操作,只是最终结果(无论成功或失败)不会再影响外层 Promise,且由于 Promise.all 内部已为每个输入附加了处理函数,后续的拒绝也不会触发 unhandledrejection。
- 任一输入拒绝即整体快速拒绝
- 其他 Promise 继续执行但结果被忽略
- 后续拒绝不会触发 unhandledrejection
拒绝传播的微任务时机
Promise.all 在内部对每个输入 Promise 调用 then,并登记计数。当某个 Promise 转为 rejected 时,它会立即调用外层 Promise 的拒绝分支,使返回值立刻拒绝。这里的“立刻”是微任务层面上的,不等待同批其他 Promise 的完成。
其他 Promise 不会被取消,因为它们已经启动了自己的异步流程。当它们之后成功或失败时,Promise.all 的内部处理器已设置为忽略其结果,因此不会覆盖已经确定的拒绝状态。同时这些处理器使每个 Promise 都被视为“已处理”,所以之后出现的 rejection 不会冒泡为 unhandledrejection。
并行拉取接口:一个 401 触发整体跳转登录
一个页面需要同时请求用户资料、好友列表和设置项,三个请求互不依赖,用 Promise.all 并发发起。若好友列表接口因会话过期返回 401,Promise.all 会立刻进入 rejected 状态,进入 catch 分支提示用户重新登录,而不等待另外两个可能几秒后才返回的请求。
此时另外两个请求的网络传输仍在进行,服务器也会正常响应,但它们的返回值不再被使用。如果后续它们自身也失败,不会触发任何错误事件,因为 Promise.all 已经给它们加了处理函数。要保留部分成功数据,应给每个子 Promise 单独附加 catch 转换成默认值,但这会改变快速失败语义。
快速失败不等于取消,也不适合需要完整结果的场合
快速失败是一种短路机制,但 Promise 本身没有取消能力。如果发起的是 HTTP 请求,请求会继续占用连接直到响应或超时。若后续逻辑依赖所有请求都完成(例如统计、日志聚合),用 Promise.all 会导致流式结果丢失,此时应改用 allSettled 等待全部落定。
另一个边界:Promise.all 接受可迭代对象但不接受函数,如果直接把 async 函数作为元素传入,它们不会执行,返回值是函数本身。遇到混合场景时,应优先保证每个子任务都能处理自身拒绝,再决定用哪个组合器。
容易答错的地方
- 其他 Promise 会被取消或停止
- 错。
Promise.all拒绝不会取消任何子 Promise,它们已经启动的异步操作会继续执行,只是结果被忽略。要真正取消需使用AbortController等机制,这不在Promise.all的能力范围内。 - 后续拒绝会触发 unhandledrejection
- 错。
Promise.all在调用时已为每个输入 Promise 附加了.then,因此所有 Promise 都被视为“已处理”。即使首个拒绝之后,其他 Promise 又拒绝,也不会触发全局的unhandledrejection事件。
面试官还会怎么问?
Promise.all 和 Promise.allSettled 在错误处理上的核心区别是什么?
Promise.all 任一拒绝就立即以该原因整体拒绝,忽略后续结果;allSettled 等待所有输入完成,每个结果无论成功或失败都会被汇总成数组,始终不会提前拒绝。
如何让 Promise.all 中某个失败不影响整体,但保留其他成功结果?
为每个子 Promise 附加 .catch,把错误转换成一个标识值或默认值,使它们永不拒绝。这样 Promise.all 只会等到所有子 Promise 都 fulfilled,结果数组可区分成功项与失败项。
Promise.all 返回的 Promise 在输入全部已拒绝时会同步拒绝吗?
不会。Promise.all 的处理始终是异步的(除非传入空数组)。即使所有输入都是已拒绝的 Promise,返回的 Promise 也要等到当前微任务队列中才能被观察,不会同步进入 rejected 状态。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。