JavaScript异步编程进化史:Callback、Promise到Async/Await

m
marvis

一、JavaScript的单线程与事件循环

JavaScript是单线程语言——一个主线程处理所有任务。这避免了多线程编程中的竞争条件和死锁问题,但也意味着一个耗时操作会阻塞整个应用。异步编程就是在单线程环境下让耗时操作(如网络请求、文件I/O、定时器)不阻塞主线程的艺术。

理解异步编程的钥匙是事件循环(Event Loop)。执行流程:执行所有同步代码 → 清空微任务队列(Microtask Queue,包括Promise.then/catch/finally、MutationObserver)→ 执行一个宏任务(Macrotask,包括setTimeout/setInterval/I/O、UI Rendering)→ 再次清空微任务队列 → 循环往复。

二、三大异步范式演进

范式引入版本核心机制典型问题
Callback浏览器早期函数作为参数传递,异步完成后调用回调地狱、错误处理混乱
PromiseES6(2015)链式调用 .then().catch(),统一错误处理长链可读性下降
Async/AwaitES2017同步风格写异步代码,try/catch错误处理滥用串行await降低性能

三、Promise高级模式

  • Promise.all():并行执行,全部成功才返回。任一失败则整体失败。适合互不依赖的并行任务
  • Promise.allSettled():并行执行,等待全部完成(无论成败)。适合需要知道每个任务结果的场景,不会因个别失败而中断
  • Promise.race():竞速,返回最先完成的结果(无论成败)。适合超时控制
  • Promise.any():返回第一个成功的结果,全部失败才reject。适合多数据源容灾

四、Async/Await的陷阱与最佳实践

4.1 串行await陷阱

最常见的性能陷阱:

// ❌ 慢:串行执行(3秒)
const user = await fetchUser();
const posts = await fetchPosts();
const comments = await fetchComments();

// ✅ 快:并行执行(1秒)
const [user, posts, comments] = await Promise.all([
    fetchUser(), fetchPosts(), fetchComments()
]);

4.2 错误处理

Async/Await的try/catch让错误处理回归直觉,但注意await在Promise创建时就开始了执行。推荐使用await-to-js模式(类似Go的错误处理):

const [err, data] = await to(fetchData());
if (err) return handleError(err);

4.3 循环中的异步

for...of + await是串行的;如需并行使用map + Promise.all。需要控制并发数时使用p-limit库信号量模式

五、2025年新趋势

  • Top-Level Await(ES2022):模块顶层直接使用await,无需包装async函数
  • Array.fromAsync()(ES2024):从异步可迭代对象创建数组
  • Promise.withResolvers()(ES2024):更简洁的Promise创建方式
  • Observable API(提案中):原生支持响应式数据流,替代部分RxJS场景

相关阅读:Go并发编程实战 | Python 3.13无GIL时代 | ES2025/2026新特性