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

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

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


推荐文章

MySQL死锁 - 更新插入导致死锁
2024-11-19 05:53:50 +0800 CST
在 Nginx 中保存并记录 POST 数据
2024-11-19 06:54:06 +0800 CST
虚拟DOM渲染器的内部机制
2024-11-19 06:49:23 +0800 CST
Flet 构建跨平台应用的 Python 框架
2025-03-21 08:40:53 +0800 CST
禁止调试前端页面代码
2024-11-19 02:17:33 +0800 CST
一个数字时钟的HTML
2024-11-19 07:46:53 +0800 CST
Golang Sync.Once 使用与原理
2024-11-17 03:53:42 +0800 CST
Python Invoke:强大的自动化任务库
2024-11-18 14:05:40 +0800 CST
go命令行
2024-11-18 18:17:47 +0800 CST
MySQL数据库的36条军规
2024-11-18 16:46:25 +0800 CST
15 个 JavaScript 性能优化技巧
2024-11-19 07:52:10 +0800 CST
介绍 Vue 3 中的新的 `emits` 选项
2024-11-17 04:45:50 +0800 CST
Vue3中的Slots有哪些变化?
2024-11-18 16:34:49 +0800 CST
Vue 中如何处理跨组件通信?
2024-11-17 15:59:54 +0800 CST
设置mysql支持emoji表情
2024-11-17 04:59:45 +0800 CST
Nginx 防止IP伪造,绕过IP限制
2025-01-15 09:44:42 +0800 CST
Vue3结合Driver.js实现新手指引功能
2024-11-19 08:46:50 +0800 CST
程序员茄子在线接单