写过前端的人都踩过这个坑——每次并发请求等结果,解构赋值后拿到的变量顺序一变代码就悄悄 break 了,Firefox 155 今天把这件事用两个新 API 彻底原生化了

写过前端的人都踩过这个坑——每次并发请求等结果,解构赋值后拿到的变量顺序一变代码就悄悄 break 了,Firefox 155 今天把这件事用两个新 API 彻底原生化了。

以前写并发请求是这样的:

const [user, posts, settings] = await Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/posts').then(r => r.json()),
  fetch('/api/settings').then(r => r.json()),
]);
renderDashboard(user, posts, settings);

这行代码里有个隐藏的脆弱点:数组是按位置对应的,user 在第 0 位,posts 在第 1 位,settings 在第 2 位。一旦哪天产品加了需求变成 [settings, user, posts],解构赋值里的三个变量名必须跟着改,否则 renderDashboard(settings, user, posts) 传入的参数名和实际内容就对不上了——TypeScript 不报错,代码跑得通,只是结果悄悄错了。

这其实是 Promise.all 设计上的一个历史遗留:它接受 iterable(数组),结果按数组索引顺序返回,所以调用方必须记住哪个位置对应哪个数据。

Firefox 155 带来了两个新方法

Promise.allKeyed()Promise.allSettledKeyed(),接受一个对象而不是数组,返回值也是一个对象,key 保持不变:

const result = await Promise.allKeyed({
  user: fetch('/api/user').then(r => r.json()),
  posts: fetch('/api/posts').then(r => r.json()),
  settings: fetch('/api/settings').then(r => r.json()),
});

// 直接按 key 访问,不再按位置
renderDashboard(result.user, result.posts, result.settings);

结果对象 result 的 shape 和输入完全一致——输入是 {user, posts, settings},输出也是这三个 key,没有顺序依赖,代码可以自由重组。

配合解构赋值更优雅:

const { user, posts, settings } = await Promise.allKeyed({
  user: fetch('/api/user').then(r => r.json()),
  posts: fetch('/api/posts').then(r => r.json()),
  settings: fetch('/api/settings').then(r => r.json()),
});
renderDashboard(user, posts, settings);

这和以前看起来差不多,但关键区别在于:userpostssettings 现在是对象的 key,编译器会强制校验 key 是否存在,漏写或拼错都会报错,而不是默默拿到 undefined

Promise.allSettledKeyed() 的区别

Promise.allKeyed() 和原版 Promise.all 一样,任一 promise reject 就整体 reject,适合「全部必须成功」的场景。

Promise.allSettledKeyed() 则像 Promise.allSettled 一样,无论每个 promise 是成功还是失败都返回,最终结果是一个对象,每个 key 对应 {status: 'fulfilled', value: ...}{status: 'rejected', reason: ...}。它适合「每个请求独立,部分失败不影响其他结果展示」的场景:

const results = await Promise.allSettledKeyed({
  user: fetch('/api/user').then(r => r.json()),
  posts: fetch('/api/posts').then(r => r.json()),
  analytics: fetch('/api/analytics').then(r => r.json()),
});

if (results.user.status === 'fulfilled') {
  renderUser(results.user.value);
}
if (results.analytics.status === 'rejected') {
  // analytics 失败不影响页面渲染,只记录日志
  console.error('analytics 加载失败:', results.analytics.reason);
}

什么时候该用

团队协作时,如果多人负责不同的 API 接入,对象式的 API 天然比数组更抗重构。一个新来的同事在并发请求里加了一个字段,只需要在自己的对象里加一行,不需要动别人的代码,也不用担心破坏了解构赋值的顺序。

这是 ES2027 规范里的新成员,目前 Firefox 155 首发,Chrome 正在跟进。其他浏览器暂时可以用 core-js 的 polyfill 先行接入。

下一步:在你的仪表盘或管理后台里扫一遍 Promise.all([...]),如果发现解构赋值的变量名和数组里的顺序有对应关系——这个模式今天可以换了。


参考来源:MDN Web Docs(Promise.allKeyed 规范文档)、AWNews(Firefox 155 发布摘要)、Microsoft Learn(Promise 对象文档)

评论区

0 条评论

登录后可评论。

阿柯·前端架构 152 阅读