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

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

为何 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 与并发,才能写出既安全又高性能的前端代码。


推荐文章

纯CSS实现3D云动画效果
2024-11-18 18:48:05 +0800 CST
你可能不知道的 18 个前端技巧
2025-06-12 13:15:26 +0800 CST
15 个 JavaScript 性能优化技巧
2024-11-19 07:52:10 +0800 CST
Rust 高性能 XML 读写库
2024-11-19 07:50:32 +0800 CST
php获取当前域名
2024-11-18 00:12:48 +0800 CST
Manticore Search:高性能的搜索引擎
2024-11-19 03:43:32 +0800 CST
企业官网案例-芊诺网络科技官网
2024-11-18 11:30:20 +0800 CST
Go 开发中的热加载指南
2024-11-18 23:01:27 +0800 CST
Golang 随机公平库 satmihir/fair
2024-11-19 03:28:37 +0800 CST
HTML + CSS 实现微信钱包界面
2024-11-18 14:59:25 +0800 CST
html夫妻约定
2024-11-19 01:24:21 +0800 CST
Vue3中如何实现状态管理?
2024-11-19 09:40:30 +0800 CST
乐观锁和悲观锁,如何区分?
2024-11-19 09:36:53 +0800 CST
PHP 唯一卡号生成
2024-11-18 21:24:12 +0800 CST
Linux 常用进程命令介绍
2024-11-19 05:06:44 +0800 CST
markdown语法
2024-11-18 18:38:43 +0800 CST
初学者的 Rust Web 开发指南
2024-11-18 10:51:35 +0800 CST
Vue3中如何处理组件间的动画?
2024-11-17 04:54:49 +0800 CST
Rust 与 sqlx:数据库迁移实战指南
2024-11-19 02:38:49 +0800 CST
地图标注管理系统
2024-11-19 09:14:52 +0800 CST
Vue 3 中的 Watch 实现及最佳实践
2024-11-18 22:18:40 +0800 CST
程序员茄子在线接单