node 之事件(events)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了node 之事件(events)相关的知识,希望对你有一定的参考价值。
参考技术A Node.js 为单线程操作,所有的异步 I/O 操作在完成时都会发送一个事件到任务队列。node的 events 模块中提供了 event.EventEmitter 对象,EventEmitter 的核心就是事件触发与事件监听器功能的封装。
EcentEmitter 对象的方法:
addListener(event, listener) : 为指定事件添加一个监听器到监听器数组的尾部。
on(event, listener) : 为指定事件注册一个监听器,接受一个字符串 event 和一个回调函数。
emit(event, [arg1], [arg2], [...]) : 按参数的顺序执行每个监听器,如果事件有注册监听返回 true,否则返false。
基本使用:
var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();
event.on('mouse_out', function()
console.log('event 事件触发!');
);
setTimeout(function()
event.emit('mouse_out');
, 1000);
执行: node event.js
结果:event 事件触发!
大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它。包括 fs、net、 http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。为什么要这样做呢?原因有两点:
首先,具有某个实体功能的对象实现事件符合语义, 事件的监听和发生应该是一个对象的方法。
其次 javascript 的对象机制是基于原型的,支持 部分多重继承,继承 EventEmitter 不会打乱对象原有的继承关系。
//定义监听事件
var listener = function(arg)
console.log("listener:" + arg);
// 引入 events 模块
var EventEmitter = require('events').EventEmitter;
//原型链实现继承
function client()
client.prototype.a=function()
console.log('aaaaaa');
client.prototype.__proto__ = EventEmitter.prototype;
var client = new client();
client.a();
client.on('listener',listener);
client.emit('listener', "hello world");
//结果:
a
listener:Hello World
node.js事件循环 event loop
Nodejs事件循环 (event loop)
node.js 事件循环的概念
当node.js 启动的时候会初始化eventloop ,每一个evnet loop 都会包含如下6个循环阶段,node.js 事件循环和浏览器事件循环完全不一样。
官网文档:https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/
- timers
- pending callbacks (I/O callbakcs)
- idle, prepare
poll
- connections
- incoming
- data, etc
- check
close callbacks
阶段概述
- times(定时器):此阶段执行那些由
setTimeout()
和setInval()
调度的回调函数。 - pending callbacks (I/O 回调):此阶段几乎会执行几乎所有的回调函数,除了close callbacks(关闭回调,不常用)和那些由 timers 与
setImmedieate()
调度的回调。 - idle, prepare(空转): 此阶段只在内部使用。
- poll(轮询): 检索新的 I/O 事件,在恰当的时候 Node 会阻塞在这个阶段。(此阶段比较复杂也是nodejs的核心 )
- check(检查):
setImmediate()
设置的回调会在此阶段执行。 - c lose callbacks(关闭事件的回调): 诸如
secket.on(close, ...)
此类的回调在此阶段被调用。
setImmediate是什么?
setImmediate 约等于 setTimeout( callback , 0)
setImmediate(() => {
console.log(2222)
})
console.log(1111)
// 打印结果
1111
2222
在事件循环的每次运行之间,Node.js 会检查它是否在等待异步 I/O 或定时器,如果没有的话就会自动关闭。
如果event loop 进入 poll 阶段,且代码未设定timer,将会发生下面的情况:
- 如果 poll queue 不为空, event loop 将同步的执行 queue 里的 callback ,直至 queue 为空,或执行的 callback 到达系统上限。
- 如果 poll queue 为空,将会发生下面情况:
- 如果代码已经被
setImmediate()
设定了 callback ,event loop 将结束poll阶段进入 check 阶段,并执行 check 阶段的 queue (check 阶段的 callback 是 setImmediate 设定的)- 如果代码没有设定
setImmediate(callback)
, event loop 将阻塞在该阶段等待 callbacks 假如poll queue 一旦到达就立即执行。如果evnet loop 进入 poll 阶段,且代码设定了 timer:
- 如果 poll queue 进入空状态时(即poll 阶段为空闲状态), event loop 将检查timers,如果有一个或多个timers 时间已经到达,event loop 将按循环顺序进入timers阶段,并执行 timer queue。
## 执行顺序
如果setTimeout 为0,实际执行时间是多少?
在nodejs中,如果setTimeout 为 0, 那么它的执行时间最少是1ms
在浏览器中,如果setTimeout 为0, 那么它的执行时间最少是 4ms
为什么 setImmediate() 与 setTimeout() 一起执行时,他们的执行顺序不确定?
setImmediate(()=>{
console.log(111)
})
setTimeout(()=>{
console.log(222)
},0)
// 111 222
// 222 111
两种情况都有
这是因为event loop 启动需要时间,而在这个启动时间具有不确定性,导致了代码执行顺序的差异。他们的执行顺序取决于event loop 的启动速度。
如果 event loop 启动后从 timer到 I/O 再到 poll 阶段 如果用了 1.5ms ,那么 timeout 的时间已经到了这是settimeout就会先执行。这时打印顺序是 222 111
如果event loop 启动后从 timer到 I/O 再到 poll 阶段 如果用了 0.8ms ,这时timeout实际执行是1ms,这时poll就会跳过timeout 进入 check 阶段执行 setImmediate。而check执行完,进入第二轮时间循环后就会执行timeout了。这时打印顺序是 111 222
process.nextTick()
process.nextTick() 不在event loop 的任何阶段,而是各个阶段切换的中间执行,即从一个阶段切换到下一个阶段前,穿插着执行。
设计原因
允许开发者通过递归调用process.nextTick()
来阻塞 I/O 操作。
应用场景
- 在多个事件里交叉执行CPU运算密集型任务:
const http = require('http')
function compute(){
process.nextTick(compute)
}
http.createServer((req, res) => {
// http服务请求的时候,还能抽空进行一些计算
res.writeHead(200, {
'Content-Type': 'text/plain'
})
res.end('hello word')
}).listen(3000, '127.0.0.1')
compute()
在这种模式下不需要递归调用compute(),只需要在事件循环中使用process.nextTick() 定义compute() 在下一个时间点执行即可。在这个过程中,如果新的http请求进来,时间循环机制会先处理新的请求,然后再调用compute , 反之,如果直接把compute()放在递归调用里,系统会一直阻塞在compute() 里,无法处理新的http请求。
保持回调函数异步执行的原则
当给一个函数定义回调时,要确保这个回调函数是被异步执行的。而下面这个例子就违反了这一原则。
function asyncFake(data, callback) {
if(data === 'foo') {
callback(true)
}
else{
callback(false)
}
}
asyncFake('bar', result => {
// this callback is actually called asynchronously!
})
在node.js 中有这样一段代码
const client = net.connect(8000, () => {
client.write('hello wrod')
})
上面代码因为某种原因, net.connect() 变成了同步执行,回调函数就会被立刻执行,因此回调函数写到客户端的变量就永远不会初始化了。在这种情况下就可以使用process.nextTick()把回调函数编程异步执行。
const client = net.connect(8000, () => {
process.nextTick(()=> {
clllback(data === 'foo')
})
})
- 用在事件触发过程中
EventEmitter 有2个比较核心的方法,on和emit,node自带发布/订阅模式
// nodejs 的发布订阅
const EventEmitter = require('events').EventEmitter
class App extends EventEmitter{
}
var app = new App()
// on 订阅
app.on('start', () => {
console.log('start')
})
// emit 触发 是个同步的方法
app.emit('start')
console.log('111')
process.nextTick() 与 setTimeout 和 setImmediate 的执行顺序
process.nextTick()的执行时间是不确定的,如果在执行过程中会穿插在每个阶段。
const fs = require('fs')
const path = require('path')
fs.readFile(path.resolve(__dirname, "/1.js"), ()=> {
setTimeout(() => {
console.log('1.timeout')
},0)
setImmediate(() => {
console.log('2.immediate')
})
process.nextTick(() => {
console.log('3.nextTick')
})
process.nextTick(() => {
console.log('4.nextTick')
})
})
// 打印结果
3.nextTick
4.nextTick
2.immediate
1.timeout
以上是关于node 之事件(events)的主要内容,如果未能解决你的问题,请参考以下文章