一、事件机制(Event Loop & EventEmitter)
1.1 什么是事件机制?
Node.js 基于事件驱动和异步非阻塞 I/O。它通过**事件循环(Event Loop)**来调度任务,保证高并发性能。
- 事件循环:主线程不断轮询任务队列,处理异步事件和回调。
- 事件触发:异步操作完成后,将回调函数加入事件队列,等待主线程处理。
1.2 EventEmitter
Node.js 提供了 events 模块,核心类是 EventEmitter,用于事件的发布与订阅。
示例:自定义事件
const EventEmitter = require('events');
const emitter = new EventEmitter();
// 监听事件
emitter.on('greet', (name) => {
console.log(`Hello, ${name}!`);
});
// 触发事件
emitter.emit('greet', 'Node.js');
常用方法
on(eventName, listener):注册事件监听emit(eventName, ...args):触发事件once(eventName, listener):只监听一次removeListener(eventName, listener):移除监听
应用场景
- 网络连接、文件读取、定时器等系统事件
- 用户自定义业务事件(如消息队列、任务调度)
二、回调(Callback)
2.1 什么是回调?
回调是函数作为参数传递,在异步操作完成后执行。Node.js 早期大量采用回调模式。
示例:异步读取文件
const fs = require('fs');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('文件内容:', data);
});
2.2 回调地狱(Callback Hell)
多层嵌套回调导致代码难以维护:
fs.readFile('a.txt', 'utf8', (err, data) => {
if (err) return;
fs.readFile('b.txt', 'utf8', (err, data2) => {
if (err) return;
// ...
});
});
2.3 优化方式
- Promise:链式调用,减少嵌套
- async/await:同步风格书写异步代码
- 事件机制:用事件管理复杂异步流程
三、事件机制与回调的关系
- Node.js 的异步 API(如 fs、net、http)底层都基于事件机制,最终通过回调通知结果。
- 事件机制适合多次触发(如 socket 数据),回调适合一次性操作(如文件读取)。
四、进阶:自定义异步流程管理
4.1 用事件机制管理异步流程
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('step1', () => {
setTimeout(() => {
console.log('第1步完成');
emitter.emit('step2');
}, 1000);
});
emitter.on('step2', () => {
setTimeout(() => {
console.log('第2步完成');
emitter.emit('done');
}, 1000);
});
emitter.on('done', () => {
console.log('所有步骤完成');
});
emitter.emit('step1');
4.2 用 Promise/async 优化回调地狱
const fs = require('fs/promises');
async function readFiles() {
const data1 = await fs.readFile('a.txt', 'utf8');
const data2 = await fs.readFile('b.txt', 'utf8');
console.log(data1, data2);
}
readFiles();
五、实践建议
- 简单异步用回调,复杂流程用事件机制或 Promise/async。
- 注意回调中的错误处理,避免程序崩溃。
- 事件机制适合模块间解耦、扩展性强的场景。
六、事件循环原理、自定义事件模块、异步流程设计
1、事件循环原理(Event Loop)
1.1 Node.js 事件循环简介
事件循环是 Node.js 异步非阻塞 I/O 的核心。它让 Node.js 可以高效处理大量并发请求。
基本流程:
- 执行主线程代码(同步代码)。
- 检查事件队列(包括回调、定时器、I/O 等)。
- 依次处理队列中的任务。
- 新的异步任务加入队列,等待事件循环调度。
- 循环往复,直到没有任务。
1.2 阶段划分(简化版)
Node.js 事件循环主要分为以下几个阶段:
- timers:处理 setTimeout/setInterval 等定时器回调。
- pending callbacks:处理一些系统操作的回调。
- idle, prepare:内部使用。
- poll:处理 I/O 事件(文件、网络等),如果无事件则可能进入下一个阶段。
- check:处理 setImmediate 回调。
- close callbacks:处理如 socket 的 close 事件。
事件循环示例
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
console.log('main');
输出顺序:
main
nextTick
timeout
immediate
process.nextTick总是优先于定时器和 setImmediate 执行。
1.3 参考资料
2、自定义事件模块
Node.js 的 events 模块允许你实现自己的事件发布/订阅系统,解耦业务逻辑。
2.1 基础用法
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const emitter = new MyEmitter();
emitter.on('data', (msg) => {
console.log('收到数据:', msg);
});
emitter.emit('data', 'Hello Event!');
2.2 进阶用法:事件参数、一次性事件、移除监听
// 只监听一次
emitter.once('finish', () => console.log('任务完成一次'));
// 移除监听
function onError(err) { console.error('错误:', err); }
emitter.on('error', onError);
emitter.removeListener('error', onError);
2.3 实践场景
- 日志系统
- 任务队列
- 微服务消息总线
- 多模块解耦通信
3、异步流程设计
异步流程设计是 Node.js 高级开发的难点,常见有三种方式:
3.1 回调方式
嵌套回调,容易形成“回调地狱”,不推荐复杂流程使用。
3.2 事件驱动方式
适合多个步骤、异步事件触发的场景。
const EventEmitter = require('events');
const workflow = new EventEmitter();
workflow.on('step1', () => {
setTimeout(() => {
console.log('step1 done');
workflow.emit('step2');
}, 500);
});
workflow.on('step2', () => {
setTimeout(() => {
console.log('step2 done');
workflow.emit('finish');
}, 500);
});
workflow.on('finish', () => {
console.log('流程结束');
});
workflow.emit('step1');
3.3 Promise/async/await 方式(现代最佳实践)
适合串行和并行异步操作,代码更简洁易维护。
const fs = require('fs/promises');
async function workflow() {
const a = await fs.readFile('a.txt', 'utf8');
const b = await fs.readFile('b.txt', 'utf8');
console.log('a:', a);
console.log('b:', b);
// 并行操作
const [c, d] = await Promise.all([
fs.readFile('c.txt', 'utf8'),
fs.readFile('d.txt', 'utf8')
]);
console.log('c:', c, 'd:', d);
}
workflow();
3.4 复杂流程设计建议
- 串行:用 async/await
- 并行:用 Promise.all
- 多步骤/多事件:用事件机制(EventEmitter)
- 错误处理:统一 try/catch 或事件监听
- 状态管理:可用状态机模式(如 xstate、machina)
4、总结
- 事件循环是 Node.js 高性能的基础,理解其阶段有助于优化异步代码。
- 自定义事件模块(EventEmitter)适合解耦、扩展和多模块通信。
- 异步流程设计推荐用 async/await + Promise,复杂场景可结合事件机制。
七、事件循环底层原理、异步调度优化、事件总线设计模式
1、事件循环底层原理
1.1 Node.js 事件循环基础
Node.js 的事件循环(Event Loop)是基于 libuv 库实现的。libuv 是一个跨平台的异步 I/O 库,负责管理事件循环、线程池、异步 I/O 等底层细节。
事件循环的核心目标:
- 让 JavaScript 单线程能高效处理大量并发 I/O 请求。
- 通过事件队列和回调机制,实现异步非阻塞。
1.2 事件循环的各个阶段
事件循环分为多个阶段,每个阶段处理不同类型的回调:
┌───────────────────────────┐
│ timers (setTimeout等) │
├───────────────────────────┤
│ pending callbacks │
├───────────────────────────┤
│ idle, prepare │
├───────────────────────────┤
│ poll (I/O事件) │
├───────────────────────────┤
│ check (setImmediate) │
├───────────────────────────┤
│ close callbacks │
└───────────────────────────┘
- timers:处理定时器(setTimeout/setInterval)回调。
- pending callbacks:处理某些系统操作的回调。
- idle, prepare:内部使用。
- poll:处理 I/O 事件,如文件、网络请求。若无事件可处理,可能进入下一个阶段。
- check:处理 setImmediate 回调。
- close callbacks:处理如 socket 的关闭事件。
注意:
process.nextTick与 Promise 微任务优先于上述所有阶段执行(属于微任务队列)。
1.3 微任务与宏任务
- 微任务队列:process.nextTick、Promise.then/catch/finally
- 宏任务队列:setTimeout、setImmediate、I/O 回调等
Node.js 每执行一个宏任务阶段后,会清空微任务队列。
代码演示
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
console.log('main');
输出顺序:
main
nextTick
promise
timeout
immediate
1.4 底层流程简化
- 执行同步代码
- 处理微任务队列(nextTick、Promise)
- 进入事件循环各阶段,依次处理对应队列的回调
- 新的异步任务加入队列,循环继续
2、异步调度优化
2.1 合理选择异步方式
- I/O 密集型任务:利用 Node.js 的异步 API(如 fs、net、http),避免阻塞主线程
- CPU 密集型任务:用 worker_threads 或 child_process 多线程/多进程处理
2.2 批量并发控制
一次性启动大量异步任务会造成资源争抢,建议限制并发数:
const pLimit = require('p-limit');
const limit = pLimit(5); // 最多5个并发
const tasks = Array.from({length: 20}, (_, i) =>
limit(() => fs.promises.readFile(`file${i}.txt`, 'utf8'))
);
Promise.all(tasks).then(results => {
console.log('所有文件读取完成');
});
2.3 微任务优先级与性能
- 过量使用 process.nextTick 可能导致主线程阻塞,影响事件循环
- 适度利用 Promise 微任务进行异步调度,避免回调地狱
2.4 事件驱动解耦
- 复杂异步流程用事件机制(EventEmitter)管理,避免深层嵌套
- 利用 once、on、removeListener 等方法灵活调度
2.5 错误处理与健壮性
- 异步代码统一 try/catch 或事件监听 error
- 防止未处理的异常导致程序崩溃
3、事件总线设计模式
3.1 事件总线是什么?
事件总线(Event Bus)是一种发布-订阅模式,允许不同模块间通过事件进行解耦通信,常用于大型应用架构。
3.2 Node.js 实现事件总线
可以用 EventEmitter 实现一个简单的事件总线:
// eventBus.js
const EventEmitter = require('events');
class EventBus extends EventEmitter {}
module.exports = new EventBus();
多个模块间通信:
// moduleA.js
const eventBus = require('./eventBus');
eventBus.on('userCreated', user => {
console.log('新用户:', user);
});
// moduleB.js
const eventBus = require('./eventBus');
eventBus.emit('userCreated', { name: 'Alice', id: 123 });
3.3 进阶:带命名空间的事件总线
class NamespaceEventBus {
constructor() {
this.buses = new Map();
}
getBus(namespace) {
if (!this.buses.has(namespace)) {
this.buses.set(namespace, new (require('events'))());
}
return this.buses.get(namespace);
}
}
const bus = new NamespaceEventBus();
bus.getBus('user').on('created', user => console.log('user created:', user));
bus.getBus('user').emit('created', {name: 'Bob'});
3.4 应用场景
- 微服务消息通信
- 前后端模块解耦
- 任务队列、异步通知
- 插件/扩展机制
4、总结与实践建议
- 理解事件循环底层原理有助于编写高性能、健壮的 Node.js 应用
- 优化异步调度要关注并发量、微任务/宏任务优先级、错误处理
- 事件总线是大型系统模块间解耦的利器,建议封装统一管理

被折叠的 条评论
为什么被折叠?



