主题
Node.js 异步编程
Node.js 的高性能源于其异步非阻塞模型。理解回调、Promise、async/await 以及事件循环的执行顺序,是写好 Node.js 的关键。
作者:yanshaodong
Callback(回调)
最早期的异步写法,将函数作为参数传入,操作完成后调用。
javascript
const fs = require('fs')
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error(err)
return
}
console.log(data)
})回调地狱(Callback Hell):多层嵌套导致代码难以维护。
javascript
fs.readFile('a', (err, a) => {
fs.readFile('b', (err, b) => {
fs.readFile('c', (err, c) => {
// 嵌套越来越深
})
}
})Promise
Promise 把"未来的值"封装为对象,用 .then / .catch 链式处理,解决嵌套问题。
javascript
const fs = require('fs').promises
fs.readFile('a.txt', 'utf8')
.then((a) => fs.readFile('b.txt', 'utf8'))
.then((b) => console.log(b))
.catch((err) => console.error(err))三种状态:pending(进行中)→ fulfilled(成功)/ rejected(失败)。
async / await
基于 Promise 的语法糖,让异步代码看起来像同步代码。
javascript
async function readAll() {
try {
const a = await fs.readFile('a.txt', 'utf8')
const b = await fs.readFile('b.txt', 'utf8')
return a + b
} catch (err) {
console.error('读取失败', err)
}
}并发优化:无依赖的异步操作应并行,而非串行 await。
javascript
// 串行(慢):a 完成才读 b
const a = await read('a')
const b = await read('b')
// 并行(快):同时发起
const [a, b] = await Promise.all([read('a'), read('b')])事件循环与执行顺序
Node.js 事件循环分为多个阶段,理解微任务与宏任务的区别至关重要。
- 微任务(Microtask):
Promise.then/queueMicrotask/process.nextTick(优先级最高)。 - 宏任务(Macrotask):
setTimeout/setInterval/setImmediate/ I/O 回调。
javascript
console.log('1') // 同步
setTimeout(() => console.log('2'), 0) // 宏任务
Promise.resolve().then(() => console.log('3')) // 微任务
process.nextTick(() => console.log('4')) // 微任务(先于 Promise)
console.log('5') // 同步
// 输出:1, 5, 4, 3, 2事件循环阶段(简化):
timers:setTimeout/setInterval回调pending callbacks:推迟的 I/O 回调idle, prepare:内部使用poll:检索新的 I/O 事件check:setImmediate回调close callbacks:关闭事件
每轮循环结束后,先清空所有微任务(含 nextTick 队列),再进入下一阶段。
错误处理
javascript
// 1. async 函数内的 try/catch
try {
await riskyOp()
} catch (err) {
// 处理
}
// 2. Promise 链统一 catch
doAsync().catch(handleError)
// 3. 进程级兜底(避免崩溃)
process.on('uncaughtException', (err) => {
console.error('未捕获异常', err)
})
process.on('unhandledRejection', (reason) => {
console.error('未处理的 Promise 拒绝', reason)
})生产环境务必监听
uncaughtException与unhandledRejection,并配合进程管理器(PM2 / Docker restart)实现自愈。
常见陷阱
- 忘记 await:
const data = fetchData()拿到的是 Promise 而非结果。 - 在循环里 await:应改用
Promise.all并行。 - 同步阻塞:在事件循环里执行重计算会卡住所有请求,应移入
worker_threads。
作者:yanshaodong