编程 为何 async/await 会“阻塞”页面?并发处理的正确姿势

2025-08-15 15:17:50 +0800 CST views 567

为何 async/await 会“阻塞”页面?并发处理的正确姿势

async/await 是现代 JavaScript 的利器,它让我们用同步的方式书写异步代码,告别回调地狱。然而,在实际开发中,很多人会遇到一个困惑场景:

我需要循环请求一个用户列表,使用 async/await 后,页面长时间白屏,直到所有请求完成才显示内容。async/await 不是非阻塞的吗?怎么会阻塞页面渲染?

本文将带你深入理解其本质,并教你正确的并发处理方法。


误区澄清:await 阻塞的不是主线程

  • 核心概念async/await 本身不会阻塞主线程,它是非阻塞的语法糖

  • 执行机制

    1. 遇到 await,当前 async 函数暂停执行
    2. 控制权交回主线程,主线程可处理其他任务(UI 渲染、事件响应等)
    3. Promise 完成后,async 函数的后续代码被放入任务队列,等待主线程空闲时继续执行

看似完美,但问题仍然存在。


真正的“阻塞感”元凶:串行执行的 await

async function fetchAllUsers(userIds) {
  const users = [];
  for (const id of userIds) {
    const user = await fetchUser(id); // 串行等待
    users.push(user);
  }
  renderUsers(users);
}

问题:

  • 5 个请求依次串行,总耗时≈5秒
  • renderUsers 必须等待所有请求完成
  • 虽然主线程未被 await 阻塞,但用户看到的是长时间未更新的页面
  • 用户体验上等同“阻塞”

并发处理的正确姿势:Promise.all

当请求之间没有依赖时,可以同时发起多个请求

const promises = userIds.map(id => fetchUser(id));
const users = await Promise.all(promises);
renderUsers(users);

效果:

  • 总耗时从 5 秒降至约 1 秒
  • UI 更快更新
  • 用户体验大幅提升

进阶:更多并发控制工具

1. Promise.allSettled:获取全部结果

const results = await Promise.allSettled([fetchUser(1), fetchUser(2), fetchUserThatFails()]);
  • 不在乎失败,只在乎每个 Promise 的最终状态
  • 返回格式示例:
[
  { status: 'fulfilled', value: { id: 1 } },
  { status: 'fulfilled', value: { id: 2 } },
  { status: 'rejected', reason: 'Error: User not found' }
]

2. Promise.race & Promise.any:谁快用谁

  • Promise.race:第一个完成(成功或失败)的结果
  • Promise.any:第一个成功(fulfilled)的结果

适合 CDN测速或优先使用最快数据源场景。


3. 控制并发数量:避免瞬间打垮服务器

当任务量巨大时(如 1000 个请求),直接 Promise.all 会瞬间发出大量请求,可能触发浏览器限制或服务器压力。

示例:并发池控制任务数量

async function limitedConcurrency(tasks, limit) {
  const results = [];
  const executing = [];

  for (const task of tasks) {
    const p = Promise.resolve().then(() => task());
    results.push(p);

    if (limit <= tasks.length) {
      const e = p.then(() => executing.splice(executing.indexOf(e), 1));
      executing.push(e);

      if (executing.length >= limit) {
        await Promise.race(executing);
      }
    }
  }

  return Promise.all(results);
}

const tasks = userIds.map(id => () => fetchUser(id));
limitedConcurrency(tasks, 3).then(users => {
  console.log('All users fetched with limited concurrency:', users);
});
  • 确保同时执行的任务数不超过 limit
  • 灵活、可控,避免瞬间打垮服务器

总结

  • async/await 不阻塞主线程,阻塞感来源于串行等待
  • 串行 await → 总耗时累加,UI 延迟更新
  • 并发执行Promise.allPromise.allSettled 等) → 高效、快速更新 UI
  • 并发池 → 控制大量请求的并发数量,保护服务器

正确理解 async/await 与并发,才能写出既安全又高性能的前端代码。


推荐文章

JavaScript中设置器和获取器
2024-11-17 19:54:27 +0800 CST
JS 箭头函数
2024-11-17 19:09:58 +0800 CST
一个收银台的HTML
2025-01-17 16:15:32 +0800 CST
15 个 JavaScript 性能优化技巧
2024-11-19 07:52:10 +0800 CST
企业官网案例-芊诺网络科技官网
2024-11-18 11:30:20 +0800 CST
WebSocket在消息推送中的应用代码
2024-11-18 21:46:05 +0800 CST
Nginx 跨域处理配置
2024-11-18 16:51:51 +0800 CST
乐观锁和悲观锁,如何区分?
2024-11-19 09:36:53 +0800 CST
底部导航栏
2024-11-19 01:12:32 +0800 CST
使用Vue 3和Axios进行API数据交互
2024-11-18 22:31:21 +0800 CST
Node.js中接入微信支付
2024-11-19 06:28:31 +0800 CST
地图标注管理系统
2024-11-19 09:14:52 +0800 CST
File 和 Blob 的区别
2024-11-18 23:11:46 +0800 CST
基于Flask实现后台权限管理系统
2024-11-19 09:53:09 +0800 CST
Python中何时应该使用异常处理
2024-11-19 01:16:28 +0800 CST
ElasticSearch简介与安装指南
2024-11-19 02:17:38 +0800 CST
百度开源压测工具 dperf
2024-11-18 16:50:58 +0800 CST
Nginx 状态监控与日志分析
2024-11-19 09:36:18 +0800 CST
XSS攻击是什么?
2024-11-19 02:10:07 +0800 CST
css模拟了MacBook的外观
2024-11-18 14:07:40 +0800 CST
程序员茄子在线接单