Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >nodejs事件和事件循环详解

nodejs事件和事件循环详解

作者头像
用户2323866
修改于 2021-06-22 02:14:11
修改于 2021-06-22 02:14:11
85500
代码可运行
举报
文章被收录于专栏:技术派技术派
运行总次数:0
代码可运行

简介

上篇文章我们简单的介绍了nodejs中的事件event和事件循环event loop。本文本文将会更进一步,继续讲解nodejs中的event,并探讨一下setTimeout,setImmediate和process.nextTick的区别。

nodejs中的事件循环

虽然nodejs是单线程的,但是nodejs可以将操作委托给系统内核,系统内核在后台处理这些任务,当任务完成之后,通知nodejs,从而触发nodejs中的callback方法。

这些callback会被加入轮循队列中,最终被执行。

通过这样的event loop设计,nodejs最终可以实现非阻塞的IO。

nodejs中的event loop被分成了一个个的phase,下图列出了各个phase的执行顺序:

每个phase都会维护一个callback queue,这是一个FIFO的队列。

当进入一个phase之后,首先会去执行该phase的任务,然后去执行属于该phase的callback任务。

当这个callback队列中的任务全部都被执行完毕或达到了最大的callback执行次数之后,就会进入下一个phase。

注意, windows和linux的具体实现有稍许不同,这里我们只关注最重要的几个phase。

问题:phase的执行过程中,为什么要限制最大的callback执行次数呢?

回答:在极端情况下,某个phase可能会需要执行大量的callback,如果执行这些callback花费了太多的时间,那么将会阻塞nodejs的运行,所以我们设置callback执行的次数限制,以避免nodejs的长时间block。

phase详解

上面的图中,我们列出了6个phase,接下来我们将会一一的进行解释。

timers

timers的中文意思是定时器,也就是说在给定的时间或者时间间隔去执行某个callback函数。

通常的timers函数有这样两种:setTimeout和setInterval。

一般来说这些callback函数会在到期之后尽可能的执行,但是会受到其他callback执行的影响。 我们来看一个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const fs = require('fs');

function someAsyncOperation(callback) {
  // Assume this takes 95ms to complete
  fs.readFile('/path/to/file', callback);
}

const timeoutScheduled = Date.now();

setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;

  console.log(`${delay}ms have passed since I was scheduled`);
}, 100);

// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(() => {
  const startCallback = Date.now();

  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
    // do nothing
  }
});

上面的例子中,我们调用了someAsyncOperation,这个函数首先回去执行readFile方法,假设这个方法耗时95ms。接着执行readFile的callback函数,这个callback会执行10ms。最后才回去执行setTimeout中的callback。

所以上面的例子中,虽然setTimeout指定要在100ms之后运行,但是实际上还要等待95 + 10 = 105 ms之后才会真正的执行。

pending callbacks

这个phase将会执行一些系统的callback操作,比如在做TCP连接的时候,TCP socket接收到了ECONNREFUSED信号,在某些liunx操作系统中将会上报这个错误,那么这个系统的callback将会放到pending callbacks中运行。

或者是需要在下一个event loop中执行的I/O callback操作。

idle, prepare

idle, prepare是内部使用的phase,这里就不过多介绍。

poll轮询

poll将会检测新的I/O事件,并执行与I / O相关的回调,注意这里的回调指的是除了关闭callback,timers,和setImmediate之外的几乎所有的callback事件。

poll主要处理两件事情:轮询I/O,并且计算block的时间,然后处理poll queue中的事件。

如果poll queue非空的话,event loop将会遍历queue中的callback,然后一个一个的同步执行,知道queue消费完毕,或者达到了callback数量的限制。

因为queue中的callback是一个一个同步执行的,所以可能会出现阻塞的情况。

如果poll queue空了,如果代码中调用了setImmediate,那么将会立马跳到下一个check phase,然后执行setImmediate中的callback。 如果没有调用setImmediate,那么会继续等待新来的callback被加入到queue中,并执行。

check

主要来执行setImmediate的callback。

setImmediate可以看做是一个运行在单独phase中的独特的timer,底层使用的libuv API来规划callbacks。

一般来说,如果在poll phase中有callback是以setImmediate的方式调用的话,会在poll queue为空的情况下,立马结束poll phase,进入check phase来执行对应的callback方法。

close callbacks

最后一个phase是处理close事件中的callbacks。 比如一个socket突然被关闭,那么将会触发一个close事件,并调用相关的callback。

setTimeout 和 setImmediate的区别

setTimeout和setImmediate有什么不同呢?

从上图的phase阶段可以看出,setTimeout中的callback是在timer phase中执行的,而setImmediate是在check阶段执行的。

从语义上讲,setTimeout指的是,在给定的时间之后运行某个callback。而setImmediate是在执行完当前loop中的 I/O操作之后,立马执行。

那么这两个方法的执行顺序上有什么区别呢?

下面我们举两个例子,第一个例子中两个方法都是在主模块中运行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});

这样运行两个方法的执行顺序是不确定,因为可能受到其他执行程序的影响。

第二个例子是在I/O模块中运行这两个方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const fs = require('fs');

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
});

你会发现,在I/O模块中,setImmediate一定会在setTimeout之前执行。

两者的共同点

setTimeout和setImmediate两者都有一个返回值,我们可以通过这个返回值,来对timer进行clear操作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const timeoutObj = setTimeout(() => {
  console.log('timeout beyond time');
}, 1500);

const immediateObj = setImmediate(() => {
  console.log('immediately executing immediate');
});

const intervalObj = setInterval(() => {
  console.log('interviewing the interval');
}, 500);

clearTimeout(timeoutObj);
clearImmediate(immediateObj);
clearInterval(intervalObj);

clear操作也可以clear intervalObj。

unref 和 ref

setTimeout和setInterval返回的对象都是Timeout对象。

如果这个timeout对象是最后要执行的timeout对象,那么可以使用unref方法来取消其执行,取消执行完毕,可以使用ref来恢复它的执行。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const timerObj = setTimeout(() => {
  console.log('will i run?');
});

timerObj.unref();

setImmediate(() => {
  timerObj.ref();
});

注意,如果有多个timeout对象,只有最后一个timeout对象的unref方法才会生效。

process.nextTick

process.nextTick也是一种异步API,但是它和timer是不同的。

如果我们在一个phase中调用process.nextTick,那么nextTick中的callback会在这个phase完成,进入event loop的下一个phase之前完成。

这样做就会有一个问题,如果我们在process.nextTick中进行递归调用的话,这个phase将会被阻塞,影响event loop的正常执行。

那么,为什么我们还会有process.nextTick呢?

考虑下面的一个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let bar;

function someAsyncApiCall(callback) { callback(); }

someAsyncApiCall(() => {
  console.log('bar', bar); // undefined
});

bar = 1;

上面的例子中,我们定义了一个someAsyncApiCall方法,里面执行了传入的callback函数。

这个callback函数想要输出bar的值,但是bar的值是在someAsyncApiCall方法之后被赋值的。

这个例子最终会导致输出的bar值是undefined。

我们的本意是想让用户程序执行完毕之后,再调用callback,那么我们可以使用process.nextTick来对上面的例子进行改写:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let bar;

function someAsyncApiCall(callback) {
  process.nextTick(callback);
}

someAsyncApiCall(() => {
  console.log('bar', bar); // 1
});

bar = 1;

我们再看一个实际中使用的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const server = net.createServer(() => {}).listen(8080);

server.on('listening', () => {});

上面的例子是最简单的nodejs创建web服务。

上面的例子有什么问题呢?listen(8000) 方法将会立马绑定8000端口。但是这个时候,server的listening事件绑定代码还没有执行。

这里实际上就用到了process.nextTick技术,从而不管我们在什么地方绑定listening事件,都可以监听到listen事件。

process.nextTick 和 setImmediate 的区别

process.nextTick 是立马在当前phase执行callback,而setImmediate是在check阶段执行callback。

所以process.nextTick要比setImmediate的执行顺序优先。

实际上,process.nextTick和setImmediate的语义应该进行互换。因为process.nextTick表示的才是immediate,而setImmediate表示的是next tick。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
深入理解 Event Loop
本文由 sugerpocket 首发于 IMWeb 社区网站 imweb.io。点击阅读原文查看 IMWeb 社区更多精彩文章。 众所周知,javascript 是单线程的,其通过使用异步而不阻塞主进程执行。那么,他是如何实现的呢?本文就浏览器与nodejs环境下异步实现与event loop进行相关解释。 浏览器环境 浏览器环境下,会维护一个任务队列,当异步任务到达的时候加入队列,等待事件循环到合适的时机执行。 实际上,js 引擎并不只维护一个任务队列,总共有两种任务 Task(macroTask): s
用户1097444
2022/06/29
5310
深入理解 Event Loop
深入nodejs的event-loop
event loop是指由libuv提供的,一种实现非阻塞I/O的机制。具体来讲,因为javascript一门single-threaded编程语言,所以nodejs只能把异步I/O操作的实现(非阻塞I/O的实现结果的就是异步I/O)转交给libuv来做。因为I/O既可能发生在很多不同操作系统上(Unix,Linux,Mac OX,Window),又可以分为很多不同类型的I/O(file I/O, Network I/O, DNS I/O,database I/O等)。所以,对于libuv而言,如果当前系统对某种类型的I/O操作提供相应的异步接口的话,那么libuv就使用这些现成的接口,否则的话就启动一个线程池来自己实现。这就是官方文档所说的:“事件循环使Node.js可以通过将操作转移到系统内核中来执行非阻塞I / O操作(尽管JavaScript是单线程的)”的意思。
coder2028
2022/10/21
7290
多图详解不同环境下的EventLoop执行机制
也许对于浏览器中的 EventLoop 大多数开发者已经耳熟能详了,掌握 EventLoop 它对于每一个前端开发者的重要性不言而言。
19组清风
2022/03/25
6420
多图详解不同环境下的EventLoop执行机制
面试题:说说事件循环机制(满分答案来了)
JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
winty
2020/03/19
4K0
面试题:说说事件循环机制(满分答案来了)
Node.js的事件循环(Event loop)、定时器(Timers)和 process.nextTick()
事件循环通过将操作分给系统内核来处理使得使用单线程的 JavaScript 的 Node.js 可以进行无阻塞 I/O 操作。
conanma
2022/01/05
1.5K0
一张图带你搞懂Node事件循环
以下全文7000字,请在你思路清晰、精力充沛的时刻观看。保证你理解后很长时间忘不掉。
xing.org1^
2021/08/31
1.3K0
一张图带你搞懂Node事件循环
我已经迷失在事件环(event-loop)中了【Nodejs篇】
我第一次看到他事件环(event-loop)的时候,我是一脸懵,这是什么鬼,是什么循环吗,为什么event还要loop,不是都是一次性的吗?
小美娜娜
2019/04/04
7880
nodejs定时器详解
只要用到引擎之外的功能,就需要跟外部交互,从而形成异步操作。由于异步操作实在太多,JavaScript 不得不提供很多异步语法。这就好比,有些人老是受打击, 他的抗打击能力必须变得很强,否则他就完蛋了。
javascript.shop
2019/09/04
1.1K0
nodejs定时器详解
破阵九解:Node和浏览器之事件循环/任务队列/异步顺序/数据结构
浏览器中,涉及的异步API有:Promise, setTomeOut,setImmediate
啦啦啦321
2019/10/23
1.2K0
破阵九解:Node和浏览器之事件循环/任务队列/异步顺序/数据结构
「Nodejs进阶」一文吃透异步I/O和事件循环
本文讲详细讲解 nodejs 中两个比较难以理解的部分异步I/O和事件循环,对 nodejs 核心知识点,做梳理和补充。
用户6835371
2021/09/06
2.2K0
NodeJs 事件循环-比官方翻译更全面
事件循环使Node.js可以通过将操作转移到系统内核中来执行非阻塞I/O操作(尽管JavaScript是单线程的)。
mrsuperli
2019/12/24
2.2K0
NodeJs 事件循环-比官方翻译更全面
【THE LAST TIME】彻底吃透 JavaScript 执行机制
首先我们需要声明下,JavaScript 的执行和运行是两个不同概念的,执行,一般依赖于环境,比如 node、浏览器、Ringo 等, JavaScript 在不同环境下的执行机制可能并不相同。而今天我们要讨论的 Event Loop 就是 JavaScript 的一种执行方式。所以下文我们还会梳理 node 的执行方式。而运行呢,是指JavaScript 的解析引擎。这是统一的。
Nealyang
2019/09/29
4610
【THE LAST TIME】彻底吃透 JavaScript 执行机制
【nodejs原理&源码赏析(7)】【译】Node.js中的事件循环,定时器和process.nextTick
事件循环是Node.js能够实现非阻塞I/O的基础,尽管JavaScript应用是单线程运行的,但是它可以将操作向下传递到系统内核去执行。
大史不说话
2019/06/19
1.2K0
【nodejs原理&源码赏析(7)】【译】Node.js中的事件循环,定时器和process.nextTick
[译]Node.js中的事件循环,定时器和process.nextTick()
虽然js是单线程的,但是事件循环会尽可能地将异步操作(offloading operations)托付给系统内核,让node能够执行非阻塞的I/O操作
腾讯IVWEB团队
2020/06/28
2.4K0
深度理解NodeJS事件循环
ALL THE TIME,我们写的的大部分javascript代码都是在浏览器环境下编译运行的,因此可能我们对浏览器的事件循环机制了解比Node.JS的事件循环更深入一些,但是最近写开始深入NodeJS学习的时候,发现NodeJS的事件循环机制和浏览器端有很大的区别,特此记录来深入的学习了下,以帮助自己及小伙伴们忘记后查阅及理解。
coder2028
2022/10/10
9820
nodejs事件和事件循环简介
熟悉javascript的朋友应该都使用过事件,比如鼠标的移动,鼠标的点击,键盘的输入等等。我们在javascript中监听这些事件,从而触发相应的处理。
程序那些事
2020/12/14
8060
理解event loop(浏览器环境与nodejs环境)
众所周知,javascript是单线程的,其通过使用异步而不阻塞主进程执行。那么,他是如何实现的呢?本文就浏览器与nodejs环境下异步实现与event loop进行相关解释。
IMWeb前端团队
2019/12/03
6800
理解event loop(浏览器环境与nodejs环境)
事件循环详解
主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)
Careteen
2022/02/14
6350
深入理解JavaScript的事件循环(Event Loop)
在两个环境下的Event Loop实现是不一样的,在浏览器中基于 规范 来实现,不同浏览器可能有小小区别。在Node中基于 libuv 这个库来实现
书童小二
2018/09/03
1.1K0
深入理解JavaScript的事件循环(Event Loop)
JS 事件循环 Node 篇
Node 中的事件循环比起浏览器中的 JavaScript 还是有一些区别的,各个浏览器在底层的实现上可能有些细微的出入;而 Node 只有一种实现,相对起来就少了一些理解上的麻烦。
用户8921923
2022/10/24
2.3K0
JS 事件循环 Node 篇
相关推荐
深入理解 Event Loop
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验