菜单

异步 JavaScript

相关源文件

目的与范围

本文档解释了 JavaScript 中的异步编程概念,涵盖了实现非阻塞代码执行的机制。它详细介绍了事件循环、消息队列、定时函数、Promise 以及 async/await 模式。有关闭包(常与异步代码一起使用)的相关信息,请参阅闭包,有关补充异步 JavaScript 的函数式编程技术的更多详细信息,请参阅高阶函数

来源: README.md396-421 README.md1007-1056

JavaScript 的执行模型

JavaScript 本质上是单线程的,这意味着它一次只能执行一段代码。当操作需要很长时间才能完成时(例如网络请求或文件操作),这会带来挑战。异步编程通过允许执行继续而不等待这些长时间运行的操作完成来解决此问题。

单线程特性与异步代码的需求

来源: README.md396-421

事件循环和消息队列

事件循环是 JavaScript 处理异步操作的机制。它会持续检查调用栈是否为空,如果为空,则从消息队列中取出第一个回调函数,将其推入调用栈以供执行。

事件循环架构

图表显示了 JavaScript 运行时环境的关键组成部分

  1. 调用栈:用于跟踪函数调用
  2. Web APIs:浏览器/Node.js 接口,负责处理异步操作
  3. 回调队列(宏任务队列):常规回调函数在此等待执行
  4. 微任务队列:Promise 和 process.nextTick 的优先级队列
  5. 事件循环:管理队列和调用栈的机制

来源: README.md396-421 README.md998-1006

任务类型和处理顺序

队列类型示例优先级行为
微任务Promise 回调(.then/.catch)、queueMicrotask()、process.nextTick()较高所有微任务在下一个宏任务开始前完成
宏任务(任务)setTimeout/setInterval 回调、I/O 操作、UI 渲染、事件回调较低每个事件循环迭代在处理完所有微任务后处理一个宏任务

来源: README.md396-421

异步定时函数

JavaScript 提供内置函数来调度代码在延迟后或在特定时间间隔执行。

setTimeout 和 setInterval

  • setTimeout:在指定的延迟后执行一次函数
  • setInterval:以固定的间隔重复执行函数
  • clearTimeout/clearInterval:取消计划的函数执行

这些定时函数不保证精确计时——它们表示最短延迟,之后回调可能会执行,具体取决于调用栈的可用性。

来源: README.md422-446

requestAnimationFrame

这个特殊的定时函数会将回调函数的执行与浏览器的重绘周期同步,使其成为动画和视觉更新的理想选择。

与 setTimeout 不同,requestAnimationFrame

  • 在标签页不活动时暂停
  • 与显示器的刷新率同步
  • 提供更流畅的动画
  • 被浏览器自动节流

来源: README.md422-446

现代异步模式

JavaScript 处理异步编程的方法随着时间的推移发生了显著变化,从回调到 Promise 再到 async/await 语法。

异步模式的演进

来源: README.md952-1006 README.md1007-1056

Promise

Promise 是表示异步操作最终完成或失败的对象。与回调相比,它们提供了更简洁的方式来处理异步代码。

Promise 可以处于三种状态之一

  • Pending:初始状态,未 fulfilled 也未 rejected
  • Fulfilled:操作成功完成
  • Rejected:操作失败

Promise 方法

  • Promise.then():处理 fulfilled 状态
  • Promise.catch():处理 rejected 状态
  • Promise.finally():无论 fulfilled 还是 rejected 都会执行
  • Promise.all():等待所有 Promise 都 resolve
  • Promise.race():等待第一个 Promise resolve 或 reject
  • Promise.allSettled():等待所有 Promise 都 settled(resolve 或 reject)
  • Promise.any():等待第一个 Promise fulfill

来源: README.md952-1006

Async/Await

Async/await 是在 Promise 之上构建的语法糖,它使异步代码看起来和行为更像同步代码,于 ES2017 引入。

Async/await 的关键方面

  • async 声明的函数会自动返回 Promise
  • await 只能在 async 函数(或具有顶层 await 的模块)内部使用
  • await 会暂停函数执行,直到 Promise resolve
  • Promise 中的错误可以使用常规的 try/catch 语法捕获

Async/await 简化 Promise 链的示例

Promise 链Async/Await 等效代码
fetchUser().then(user => { return fetchProfile(user); }).then(profile => { displayProfile(profile); }).catch(err => { handleError(err); })async function loadProfile() { try { const user = await fetchUser(); const profile = await fetchProfile(user); displayProfile(profile); } catch (err) { handleError(err); } }

来源: README.md1007-1056

异步 JavaScript 最佳实践

模式选择指南

场景推荐模式原理
简单延迟setTimeout对基本计时需求简单明了
动画requestAnimationFrame针对与浏览器同步的视觉更新进行优化
顺序异步操作async/await线性操作中最具可读性的方法
并发操作Promise.all/Promise.allSettled高效处理多个并行任务
事件处理回调(通过 addEventListener)事件驱动编程的标准
错误传播带 try/catch 的 async/await最简洁的错误处理语法

常见陷阱与解决方案

  1. 阻塞事件循环:避免主线程上长时间运行的操作

    • 解决方案:将任务分解成更小的块或使用 Web Workers
  2. 回调地狱:深度嵌套的回调产生难以阅读的代码

    • 解决方案:使用 Promise 或 async/await 来扁平化代码
  3. 未处理的 Promise 拒绝:遗漏的错误处理

    • 解决方案:始终使用 .catch() 或 await 的 try/catch
  4. 内存泄漏:遗忘的计时器或事件监听器

    • 解决方案:始终使用 clearTimeout/clearInterval 清除计时器,并在不再需要时移除事件监听器
  5. 竞态条件:异步操作时序不一致

    • 解决方案:使用 Promise.all、Promise.race 或通过 async/await 进行仔细同步

来源: README.md952-1006 README.md1007-1056 README.md422-446

性能考量

异步操作会带来开销和复杂性。请考虑这些性能方面

  1. 微任务与宏任务:Promise 回调(微任务)在 setTimeout 回调(宏任务)之前运行
  2. 防抖和节流:限制事件处理函数执行的速率
  3. 批处理:将多个异步操作分组以减少开销
  4. 取消:取消不必要的操作以节省资源

来源: README.md422-446 README.md396-421

结论

异步 JavaScript 是创建响应式 Web 应用程序的基础。从回调到 Promise 再到 async/await 的演进,使得异步代码越来越易读和易于维护。理解事件循环、消息队列和各种异步模式,使开发人员能够为现代 JavaScript 应用程序编写高效的非阻塞代码。

本文档中涵盖的概念为使用 JavaScript 的异步功能奠定了基础。有关更高级的主题,请参阅有关Promiseasync/await的相关 Wiki 页面。

来源: README.md396-421 README.md952-1006 README.md1007-1056