TypeScript 并发与事件模型
JavaScript/TypeScript 的并发模型以单线程事件循环(Event Loop)为核心,通过异步非阻塞 I/O 实现高并发。
跟 Python 比,TS 的并发模型更简单——只有一条主线:事件循环。没有 GIL,没有 asyncio.run() 和普通 def 的割裂,几乎所有 I/O 都天然返回 Promise。在 Python 里”并发模型选择题”(asyncio? 多线程?multiprocessing?)在 TS 里是内建答案。
事件循环:单线程的秘密武器
为什么单线程能高并发
Node.js/浏览器中的 JavaScript 是单线程的——同一时刻只执行一段代码。但这不代表它只能处理一个请求。秘密在于非阻塞 I/O + 事件驱动:
你的 JS 代码(单线程)
│
├── 发起 I/O 请求(文件读/网络/数据库)
│ │
│ └──→ 交给 libuv(C++ 线程池)处理
│ │
│ └──→ I/O 完成 → 回调放入任务队列
│ │
├── 继续处理其他请求 │
│ │
└── 事件循环检查任务队列 ←─────────────────┘
有任务? → 取出执行 → 回到循环
关键:JS 代码本身是单线程的,但 I/O 操作由底层 libuv(C++ 线程池)异步执行。CPU 密集计算才真正阻塞事件循环。
事件循环的六个阶段(Node.js)
graph LR TIMERS["① timers<br/>setTimeout/setInterval"] --> PENDING["② pending callbacks<br/>系统操作回调"] PENDING --> IDLE["③ idle/prepare<br/>内部使用"] IDLE --> POLL["④ poll<br/>I/O 回调"] POLL --> CHECK["⑤ check<br/>setImmediate"] CHECK --> CLOSE["⑥ close callbacks<br/>close 事件"] CLOSE --> TIMERS
每个阶段执行完自己的回调队列后,检查是否有 process.nextTick 和微任务需处理,然后进入下一阶段。
宏任务与微任务:执行顺序的决定因素
两类任务
| 任务类型 | 代表 | 执行时机 |
|---|---|---|
| 宏任务 (MacroTask) | setTimeout、setInterval、setImmediate、I/O | 每个事件循环阶段执行一个 |
| 微任务 (MicroTask) | Promise.then/catch/finally、process.nextTick | 每个宏任务执行完后立即清空微任务队列 |
经典面试题(也是真实踩坑题)
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
process.nextTick(() => console.log("4")); // Node.js 专有
console.log("5");
// 输出顺序:1 → 5 → 4 → 3 → 2
//
// 解析:
// 1. 同步代码先执行:1, 5
// 2. 微任务优先于宏任务:nextTick(4) > Promise(3)
// 3. 最后宏任务:setTimeout(2)Python 对照:TS 的微任务 ≈ Python asyncio 中
await后的协程恢复。区别在于 Python 的asyncio需要显式await和async def,TS 的 Promise 链可以”隐性”创建微任务——.then()调用即使不 await 也会排队执行。
Promise 与 async/await
Promise:异步操作的容器
// Promise 的三个状态:pending → fulfilled / rejected
const promise = new Promise<string>((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve("成功");
} else {
reject(new Error("失败"));
}
}, 1000);
});
promise
.then((data) => console.log(data))
.catch((err) => console.error(err.message))
.finally(() => console.log("清理"));async/await:同步风格的异步代码
// async 函数返回 Promise
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json(); // 类型:Promise<User>
}
// 错误处理:try/catch 自然捕获
async function main() {
try {
const user = await fetchUser("abc");
console.log(user.name);
} catch (err) {
console.error("获取用户失败:", err);
}
}Promise 组合:并发控制
// 并行执行 — 全部成功才成功
const [user, posts, settings] = await Promise.all([
fetchUser("abc"),
fetchPosts("abc"),
fetchSettings("abc"),
]);
// 竞速 — 最快返回的生效
const result = await Promise.race([
fetch("/api/fast"),
timeout(3000),
]);
// 全部完成(不管成败)— 查看每个的结果
const results = await Promise.allSettled([
fetch("/api/may-fail-1"),
fetch("/api/may-fail-2"),
]);
for (const r of results) {
if (r.status === "fulfilled") {
console.log(r.value);
} else {
console.error(r.reason);
}
}与 Python asyncio 的对照
| 维度 | TS Promise/async | Python asyncio |
|---|---|---|
| 异步关键字 | async function / await | async def / await |
| 并发原语 | Promise.all / Promise.race / Promise.allSettled | asyncio.gather / asyncio.wait |
| 启动方式 | 直接调用 async 函数 | 需要事件循环 asyncio.run() |
| 错误传播 | 自动(Rejection 冒泡) | 自动(异常冒泡到 await 处) |
| 任务取消 | AbortController(Web 标准) | task.cancel() |
| 生态系统 | 几乎所有库都支持 Promise | 需要 async 版本(aiofiles, aiohttp) |
经验迁移:如果你已经理解 Python 的
asyncio,TS 的 Promise/async-await 在心智模型上高度一致。最大区别是 TS 默认就是异步优先——几乎所有 I/O 操作都返回 Promise,你不需要像 Python 那样区分”同步版”和”异步版”的库。
Worker Threads:多线程的出口
为什么需要 Worker Threads
事件循环的软肋是 CPU 密集计算——一段耗时 5 秒的同步计算会阻塞整个事件循环,让所有并发请求排队等待。
// 这会阻塞事件循环 5 秒
function heavyComputation(n: number): number {
let result = 0;
for (let i = 0; i < n; i++) {
result += Math.sqrt(i);
}
return result;
}
app.get("/compute", (req, res) => {
const result = heavyComputation(1e8); // 阻塞!所有请求都在等
res.json({ result });
});Worker Threads 的基本用法
// main.ts — 主线程
import { Worker } from "worker_threads";
function computeInWorker(data: number): Promise<number> {
return new Promise((resolve, reject) => {
const worker = new Worker("./worker.js", {
workerData: data,
});
worker.on("message", resolve);
worker.on("error", reject);
worker.on("exit", (code) => {
if (code !== 0)
reject(new Error(`Worker stopped with exit code ${code}`));
});
});
}
// 使用
const result = await computeInWorker(1e8);// worker.ts — 工作线程
import { parentPort, workerData } from "worker_threads";
function compute(n: number): number {
let result = 0;
for (let i = 0; i < n; i++) {
result += Math.sqrt(i);
}
return result;
}
const result = compute(workerData);
parentPort?.postMessage(result);Worker Threads vs 子进程
| 维度 | Worker Threads | child_process |
|---|---|---|
| 内存共享 | ✅ SharedArrayBuffer | ❌ 独立内存 |
| 通信方式 | postMessage(结构化克隆) | IPC(串行化) |
| 启动开销 | 低 | 高 |
| 隔离性 | 低(共享进程) | 高(独立进程) |
| 适用场景 | CPU 密集计算 | 运行外部程序/高风险任务 |
与 Python 并发的对照
| 维度 | TS | Python |
|---|---|---|
| 默认模型 | 单线程事件循环 | 多线程(GIL)/ 多进程 / asyncio |
| CPU 密集解法 | Worker Threads | multiprocessing / 3.14t 线程 |
| I/O 密集解法 | 事件循环(内置) | asyncio(需要 async 生态) |
| 共享内存 | SharedArrayBuffer(受限) | multiprocessing.Value/Array |
| 并发复杂度 | 低(事件循环简化了大量问题) | 中(需选择 GIL 旁路方案) |
TS 的并发模型比 Python 更”纯粹”——一个模型(事件循环)覆盖 90% 场景,Worker Threads 覆盖剩余 10%。Python 的 GIL/asyncio/multiprocessing 三套方案让开发者在并发问题上花更多时间做”选项分析”。这是 TS 作为”为 Web 而生”的语言的先天优势。
常见并发陷阱
1. Promise 忘记 await
// 不会等待,result 是 Promise<User>,不是 User
async function bad() {
const result = fetchUser("abc"); // 类型:Promise<User>
console.log(result.name); // 编译期可能不报错(取决于类型),运行时出错
}
// 必须 await
async function good() {
const result = await fetchUser("abc");
console.log(result.name);
}2. 循环中的 async
// 串行执行 — 每次等上一个完成
for (const id of ids) {
await processItem(id); // 总共耗时 = sum(每次耗时)
}
// 并行执行 — 全部同时发起
await Promise.all(ids.map(id => processItem(id)));
// 并发限流 — 最多 3 个同时执行
import pLimit from "p-limit";
const limit = pLimit(3);
await Promise.all(ids.map(id => limit(() => processItem(id))));3. 未处理的 Promise Rejection
// 没有 .catch() 或 try/catch — 静默失败
fetchData().then(processResult);
// 始终处理错误
fetchData()
.then(processResult)
.catch(handleError);
// 或全局兜底
process.on("unhandledRejection", (reason) => {
console.error("未处理的 Promise 拒绝:", reason);
});关联
- meta/并发模型 — 跨语言并发哲学对比
- TS 运行时模型 — Node/Deno/Bun 的事件循环差异
- Python 并发与内存模型 — Python 侧 GIL/asyncio/多进程
- TS 类型系统 — Promise 泛型与类型推导
- TypeScript 专题 MOC
- Python: 并发与内存模型 — GIL/asyncio/多进程对照
- 范式: 响应式与事件驱动 — 事件驱动范式