TypeScript 中的并发:线程、协程与 async/await — 实战解析

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

TypeScript 中的并发:从事件循环到 Web Workers

\n\n

您是否曾注意到,在执行复杂的数学运算或处理大型 JSON 时,您的浏览器或 Node.js 应用会“卡住”?那种界面停止响应点击、服务器不再处理新请求的无助感,想必并不陌生。这一切的根源在于,JavaScript(及其严格的兄弟 TypeScript)默认是单线程语言。但这并不意味着我们的程序注定缓慢。恰恰相反,合理运用 TypeScript 中的并发 能够帮助我们构建快速且响应灵敏的应用。

\n\n

本文不会仅仅罗列术语。我们将深入剖析 async/await 的实际工作原理,揭开 事件循环 的底层面纱,探讨 TypeScript 中能否像 Go 语言一样使用“协程”,以及何时应通过 Web Workers 来运用真正的 线程。让我们开始吧!

\n\n

1. 异步 vs 并行:有何区别?

\n\n

在深入代码之前,我们先明确一下术语。很多人会混淆这两个概念,这并不好。

\n\n
    \n
  • 异步(并发) — 指程序能够启动一个任务,在等待(例如等待服务器响应)时切换到另一个任务,之后再返回继续执行的能力。在任何一个时刻,只有一条指令在执行。
  • \n
  • 并行 — 指两个或多个任务在 CPU 的不同核心上真正同时执行。
  • \n
\n\n

TypeScript 原生提供的是异步能力。而并行则需要创建独立的线程——在浏览器中是 Web Workers,在 Node.js 中是 worker_threads。请牢记这一区别,否则您可能会编写出因阻塞主线程而导致“卡顿”的代码。

\n\n

2. 事件循环与回调——TypeScript 并发的基石

\n\n

JavaScript/TypeScript 异步的核心秘密在于 事件循环。想象一下,您有一个任务队列。主线程取出一个任务并执行,如果是同步任务,则立即返回结果。如果是异步操作(例如 setTimeout 或 fetch),它会被“发送到一旁”(Web API 或 libuv),而主线程则继续执行后续代码。

\n\n

当异步操作完成时,其回调会被放入队列(任务队列或微任务队列)。事件循环会不断检查调用栈是否为空,如果为空,则从队列中取出第一个任务并执行。

\n\n
// 一个简单的示例,演示此过程\nconsole.log('1. 开始');\n\nsetTimeout(() => {\n    console.log('3. 定时器触发');\n}, 0);\n\nPromise.resolve().then(() => {\n    console.log('2. 微任务执行');\n});\n\nconsole.log('4. 结束');\n\n// 输出顺序:1, 4, 2, 3
\n\n

请注意:Promise(微任务)的执行早于 setTimeout(宏任务),尽管定时器的延迟为 0 毫秒。这对于 TypeScript 优化 来说是一个重要的知识点。

\n\n

3. Async/Await——改变一切的语法糖

\n\n

自 ES2017 问世以来,JS 世界被划分为“之前”和“之后”。Async/await 是基于 Promise 的语法糖,它使异步代码看起来像同步代码。它不会增加线程,但能极大地提升代码的可读性。

\n\n
// 模拟延迟的函数\nfunction delay(ms: number): Promise<void> {\n    return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n// 使用 async/await 的异步函数\nasync function fetchUserData(userId: number): Promise<string> {\n    console.log(`正在请求用户 ${userId} 的数据...`);\n    await delay(2000); // 模拟网络请求\n    return `用户 ${userId} 的数据`;\n}\n\nasync function main() {\n    console.log('开始');\n    const user1 = await fetchUserData(1);\n    console.log(                

博客

书籍推荐