Node.js 异步编程常见错误与最佳实践

发布于2026-08-21 14:49 阅读12次 梳理 Node.js 异步编程中最常见的错误和最佳实践,涵盖回调地狱、Promise 链、async/await 错误处理、并发控制、内存泄漏等,附代码对比示例,帮助开发者写出健壮的异步代码。
# Node.js 异步编程常见错误与最佳实践
## 前言
Node.js 的核心是异步,但异步也最容易埋坑:回调地狱、错误被吞、并发失控。本文梳理最常见的异步编程错误和对应的最佳实践。
## 错误1:回调地狱
```javascript
// 错误:层层嵌套
getUser(id, (err, user) => {
getOrders(user.id, (err, orders) => {
getDetails(orders[0].id, (err, details) => {
// 深层嵌套,难以维护
});
});
});
// 正确:async/await
const user = await getUser(id);
const orders = await getOrders(user.id);
const details = await getDetails(orders[0].id);
```
## 错误2:忘记 await
```javascript
// 错误:promise 未 await,结果 undefined
async function main() {
const result = fetchData(); // 漏了 await
console.log(result); // Promise 对象,不是数据
}
// 正确
const result = await fetchData();
```
可以用 ESLint 的 `require-await` 和 `no-floating-promises` 规则提前发现。
## 错误3:错误处理不完整
```javascript
// 错误:try-catch 只包了一部分
async function main() {
const user = await getUser(id); // 这行可能抛错
try {
const orders = await getOrders(user.id);
} catch (err) {
console.log(err);
}
}
// 正确:统一 try-catch 或错误边界
async function main() {
try {
const user = await getUser(id);
const orders = await getOrders(user.id);
} catch (err) {
console.error(err);
// 记录日志、返回错误响应
}
}
```
## 错误4:循环中的串行 await
```javascript
// 错误:循环里 await,串行执行,慢
for (const id of ids) {
await process(id);
}
// 正确:Promise.all 并发执行
await Promise.all(ids.map(id => process(id)));
// 注意:Promise.all 有一个失败全失败,
// 需要容错用 allSettled
const results = await Promise.allSettled(ids.map(process));
```
## 错误5:并发无限制
```javascript
// 危险:同时发起 1 万个请求,打爆资源
await Promise.all(hugeList.map(fetch));
// 正确:限制并发数
async function mapLimit(items, limit, fn) {
const results = [];
const queue = [...items];
async function worker() {
while (queue.length) {
const item = queue.shift();
results.push(await fn(item));
}
}
await Promise.all(Array(limit).fill().map(worker));
return results;
}
await mapLimit(hugeList, 10, fetch);
```
## 错误6:事件监听器累积
```javascript
// 错误:每次调用都添加监听器
function setup() {
emitter.on("data", handler);
}
// 正确:用 once 或显式移除
emitter.once("data", handler);
// 或
emitter.on("data", handler);
emitter.off("data", handler);
```
## 最佳实践总结
### 统一错误处理中间件
```javascript
// Express 全局错误处理
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: "Internal Server Error" });
});
// 全局捕获未处理的 Promise 异常
process.on("unhandledRejection", (reason) => {
console.error("Unhandled Rejection:", reason);
});
```
### 超时控制
```javascript
function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout")), ms)
),
]);
}
await withTimeout(fetchData(), 5000);
```
## 小结
异步编程的关键:用 async/await 替代回调、始终处理错误、控制并发数、避免循环串行。记住 Promise.all 和 allSettled 的区别,配合超时控制和全局错误处理,代码健壮性会大幅提升。