Nodejs-2

Node.js 事件循环

事件循环是Node.js处理非阻塞I/O操作的核心机制,使得单线程能够高效处理多个并发请求

Node.js的运行环境是单线程的,但它能处理并发操作,主要依赖事件循环和回调函数。

事件循环的工作原理是不断地检查任务队列,并执行其中的任务,同事处理I/O操作和定时器等

事件循环的基本原理

1.执行栈:当Node.js启动时,首先会执行全局代码(例如模块的顶层代码),这段代码会被放入执行栈中
2.任务队列:在执行栈执行完毕后,事件循环会检查任务队列(任务队列包括回调函数,定时器等),并将它们逐个放入执行栈中执行
3.异步操作:当异步操作完成时(例如,文件读取,网络请求等),他们的回调函数会被放入相应的任务队列中,等待事件循环处理

事件循环的阶段

1.定时器阶段(Timer)
事件循环会执行所有已到期的定时器(例如,setTimeout和setInterval)的回调
如果有多个定时器到期,按照它们被设置的顺序依次执行

2.I/O回调阶段(I/O callbacks)
这个阶段会执行大部分的I/O操作的回调,例如网络请求,文件读取等
这个阶段不会执行定时器的回调

3.闲置和准备阶段(Idle,prepare)
这个阶段主要用于内部操作,通常不涉及用户代码

4.轮询阶段(Poll)
这个阶段事件循环会检查是否有新的I/O事件发送,如果有,则执行这些事件的回调
如果没有I/O事件,事件循环会检查是否有定时器到期,如果有,会转到定时器阶段
如果也没有定时器到期,事件循环会进入休眠状态,直到有新的I/O事件到达

5.检查阶段(Check)
在这个阶段,setImmediate 的回调会被执行。
setImmediate 是一个用于在当前事件循环结束后执行的 API。

6.关闭事件阶段(Close callbacks)
在这个阶段,事件循环会处理一些关闭事件的回调,例如关闭 socket 的回调。

宏任务和微任务

宏任务是指在事件循环中执行的较大单位的工作。每当事件循环进入一个新的阶段时,都会处理一个宏任务。常见的宏任务包括:
1.setTimeout
2.setInterval
3.I/O操作的回调
4.setImmediate(在Node,js中)
5.requestAnimationFrame(在浏览器中)
每当事件循环进入一个新的阶段时,事件循环都会从宏任务队列中取出一个任务并执行

微任务是指在当前执行上下文完成后,下一个宏任务开始之前要执行的任务。
微任务通常用于处理异步操作的结果,常见的微任务包括:
Promise 的 .then()、.catch() 和 .finally() 回调
process.nextTick()

微任务的优先级高于宏任务。也就是说,在每个宏任务执行后,事件循环会检查微任务队列并执行所有的微任务,直到微任务队列为空,然后再开始下一个宏任务。

事件循环的执行顺序

事件循环的执行顺序可以总结为以下几个步骤:
1.执行全局代码。
2.执行一个宏任务(从宏任务队列中取出)。
3.执行所有微任务(从微任务队列中取出,直到队列为空)。
4.回到步骤 2,继续执行下一个宏任务,重复上述过程。

事件驱动程序

在Node.js中,事件驱动编程主要通过EventEmitter类来实现,EventEmitter是一个内置类,位于events模块中,通过继承EventEmitter可以创建自定义的事件发射器,并注册和触发事件
事件驱动流程如下,类似于观察者模式。事件相当于一个主题(Subject),而所有注册到这个事件上的处理函数相当于观察者(Observer)
EventEmitter

事件(Event)

事件是指某个特定的动作或状态变化。例如一个网络请求被接收时,或者文件读取完成时,这些都是事件。事件可以是用户操作,系统操作或其他程序行为

事件触发器(EventEmitter)

Nodejs的EventEmitter是事件触发器,用于实现事件驱动的编程。EventEmitter是一个可以发射事件的对象,允许注册事件监听器,并在事件发生时通知这些监听器。

创建事件触发器
const EventEmitter = require('events');
class MyEmitter extends EventEmitter{}
const myEmitter = new MyEmitter();
触发事件

使用emit方法可以触发事件。触发事件时,可以传递参数给事件处理器。

myEmitter.emit('eventName',arg1,arg2);
事件处理器(Event Handler)

事件处理器是响应事件的函数,当某个函数被触发时,注册到该事件上的处理器会被调用。可以使用on方法注册事件处理器。

myEmitter.on('eventName',(arg1,arg2)=>{
     console.log(`Event triggered with args: ${arg1}, ${arg2}`);
});

事件处理器的其他方法

  1. once
    如果你希望某个事件处理器只被调用一次,可以使用 once 方法来注册该处理器。处理器在事件触发后会被自动移除。
myEmitter.once('greet', (name) => {
    console.log(`Hello, ${name}! This will only be logged once.`);
});

myEmitter.emit('greet', 'Charlie'); // 输出: Hello, Charlie! This will only be logged once.
myEmitter.emit('greet', 'Dave');     // 不会输出任何内容
  1. removeListener 和 removeAllListeners
    如果你希望移除某个特定的事件处理器,可以使用 removeListener 方法。要移除所有的事件处理器,可以使用 removeAllListeners 方法。
const handler = (name) => {
    console.log(`Hello, ${name}!`);
};

myEmitter.on('greet', handler);
myEmitter.emit('greet', 'Eve'); // 输出: Hello, Eve!

// 移除特定的事件处理器
myEmitter.removeListener('greet', handler);
myEmitter.emit('greet', 'Frank'); // 不会输出任何内容

事件的错误处理
在 Node.js 中,EventEmitter 类还提供了一个特殊的事件 error。如果在事件处理器中发生错误,建议使用 error 事件进行处理。未处理的 error 事件会导致程序崩溃。

myEmitter.on('error', (err) => {
    console.error('An error occurred:', err);
});

// 触发 error 事件
myEmitter.emit('error', new Error('Something went wrong!'));

一个小demo

var events = require('events')

// 创建EventEmitter对象
var eventEmitter = new events.EventEmitter();

// 创建事件处理程序
var connectHandler = function connected(){
    console.log('连接成功');
    eventEmitter.emit('data_received');
}

eventEmitter.on('connection',connectHandler);

eventEmitter.on('data_received',function(){
    console.log('数据接收成功');
});

eventEmitter.emit('connection');
console.log("程序执行完毕。");