目录
Event Loop
什么是Event Loop? 简单来说就是
处理Event的Loop
那么 什么是Loop呢? 简单来说就是
Loop就是一个死循环
死循环多简单 谁都会写
while(1) {
// 处理event
}
当然 这是不实用的loop
因为 没有event的时候 还总是这么执行死循环 会对资源造成极大的消耗 结果就是设备变慢了�电量消耗了
所以 通常情况下 会对loop加入休眠-唤醒机制
Node.js Event Loop最准确的图示来自官方文档The Node.js Event Loop, Timers, and process.nextTick()
当然 还有一个更好理解但可能不适用于最新Node.js版本的图示@shigeki
但是 我的图应该还是最轻松 + 最好理解的 (为了实现最好理解 下图牺牲了一些准确性)
-----------> timers
| |
| | <--- ① setTimeout()/setInterval()
| |
| I/O callbacks
| |
| | <--- ③ process.nextTick()
| |
| current operation
| |
| | <--- ③ process.nextTick()
| |
| poll queue
| |
| | <--- ③ process.nextTick()
| |
| check
| |
| | <--- ② setImmediate() <--- ③ process.nextTick()
| |
---------------
从上图我们可以得到以下最直观的认识
process.nextTick() 早于 setImmediate() 早于 setTimeout()/setInterval()
setTimeout()/setInterval()
setTimeout()/setInterval()应该是很常用也很好理解的: 用法设置一个定时器 延迟定时执行某一任务
两个的区别在于: 前者指定的任务是一次性执行 后者则为反复执行
因此 本文就不赘述了 只是有一点需要读者注意的是 如果想要实现每次页面重绘时执行任务
优先选择requestAnimationFrame接口来替代setTimeout()
setImmediate()/process.nextTick()
比较难理解的应该setImmediate()与process.nextTick() 从上面的介绍来看
两者都可以实现异步 只是执行时间 process.nextTick() 早于 setImmediate()
官方的结论比较简单直接
尽可能使用setImmediate()而不是process.nextTick() 因为 更好定位问题且兼容性浏览器环境
那么process.nextTick()是没有存在的意义了呢? 当然不是 否则这个接口早就被删除了好吧
触发事件
例如 实现一个读取文件的库 在读取文件之前发送start事件 在读取文件后发送data消息 实现代码如下
var fs = require('fs');
var EventEmitter = require('events').EventEmitter;
function StreamLibrary(resourceName) {
var self = this;
self.emit('start');
fs.readFile(resourceName, function (err, data) {
self.emit('data', data);
});
}
StreamLibrary.prototype.__proto__ = EventEmitter.prototype; // inherit from EventEmitter
使用这个库的应用程序代码如下
var stream = new StreamLibrary('./demo.txt');
stream.on('start', function () {
console.log('Reading has started');
});
stream.on('data', function (chunk) {
console.log('Received: ' + chunk);
});
上述代码中的demo.txt是在代码执行目录添加的测试文件 内容为"demo"
使用babel-node执行该段代码 打印结果如下
Received: demo
关于babel-node的更多介绍请参考JavaScript学习 之 版本
我们发现start事件没有打印 这是因为在start事件emit的时候(StreamLibrary对象创建时) 此时事件的回调函数还没有准备好(stream.on('start', callback))
因此 我们需要使用process.nextTick()来保证事件的时序 修改后的StreamLibrary.js代码如下
function StreamLibrary(resourceName) {
var self = this;
process.nextTick(function () {
self.emit('start');
});
fs.readFile(resourceName, function (err, data) {
self.emit('data', data);
});
}
再次使用babel-node执行该段代码 打印结果如下
Reading has started
Received: demo
交叉执行
基于JavaScript的Node.js其实并不擅长运算密集型的任务 但是 如果确实有这样的情况 可以采用这面的方式来处理
var http = require('http');
function compute() {
// performs complicated calculations continuously
// ...
process.nextTick(compute);
}
http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World');
}).listen(5000, '127.0.0.1');
compute();
这个应用模式就像一个单线程的web server 在这里我们就可以使用process.nextTick()来交叉执行compute()和正常的事件响应
在这个过程中 如果有新的http请求进来 事件循环机制会先处理新的请求 然后再调用compute()
小结
如果只是应用层编码的话 我们主要了解的是setTimeout()/setInterval()以及setImmediate()
如果还要设计库开发的话 我们还需要掌握process.nextTick()的原理和使用
当然 也会有一些面试官�和面试者为了互相"敷衍" 问这样的问题
递归调用process.nextTick()会发生什么?
答案参考这里的eventloop下的nextTick.js
无论问或不问 了解JavaScript和Node.js的Event Loop对于深入理解环境和系统都是有很大帮助的
参考
更多文章, 请支持我的个人博客