Node定时器详细解析

javascript 是单线程运行,异步操作特别重要。本文主要和大家介绍了node 定时器的相关知识,只要用到引擎之外的功能,就需要跟外部交互,从而形成异步操作。由于异步操作实在太多,javascript 不得不提供很多异步语法。

Node 的异步语法比浏览器更复杂,因为它可以跟内核对话,不得不搞了一个专门的库 libuv 做这件事。这个库负责各种回调函数的执行时间,毕竟异步任务最后还是要回到主线程,一个个排队执行。

Node定时器详细解析

为了协调异步任务,Node 居然提供了四个定时器,让任务可以在指定的时间运行。

setTimeout()

setInterval()

setImmediate()

process.nextTick()

前两个是语言的标准,后两个是 Node 独有的。它们的写法差不多,作用也差不多,不太容易区别。

你能说出下面代码的运行结果吗?

// test.jssetTimeout(() => console.log(1));setImmediate(() => console.log(2));process.nextTick(() => console.log(3));Promise.resolve().then(() => console.log(4));(() => console.log(5))();

登录后复制

运行结果如下。

$ node test.js

如果你能一口说对,可能就不需要再看下去了。本文详细解释,Node 怎么处理各种定时器,或者更广义地说,libuv 库怎么安排异步任务在主线程上执行。

一、同步任务和异步任务

首先,同步任务总是比异步任务更早执行。

前面的那段代码,只有最后一行是同步任务,因此最早执行。

(() => console.log(5))();

二、本轮循环和次轮循环

异步任务可以分成两种。

追加在本轮循环的异步任务
追加在次轮循环的异步任务

所谓”循环”,指的是事件循环(event loop)。这是 JavaScript 引擎处理异步任务的方式,后文会详细解释。这里只要理解,本轮循环一定早于次轮循环执行即可。

Node 规定,process.nextTick和Promise的回调函数,追加在本轮循环,即同步任务一旦执行完成,就开始执行它们。而setTimeout、setInterval、setImmediate的回调函数,追加在次轮循环。

这就是说,文首那段代码的第三行和第四行,一定比第一行和第二行更早执行。

// 下面两行,次轮循环执行setTimeout(() => console.log(1));setImmediate(() => console.log(2));// 下面两行,本轮循环执行process.nextTick(() => console.log(3));Promise.resolve().then(() => console.log(4));

登录后复制

三、process.nextTick()

process.nextTick这个名字有点误导,它是在本轮循环执行的,而且是所有异步任务里面最快执行的。

Node定时器详细解析

Node 执行完所有同步任务,接下来就会执行process.nextTick的任务队列。所以,下面这行代码是第二个输出结果。

process.nextTick(() => console.log(3));

登录后复制

基本上,如果你希望异步任务尽可能快地执行,那就使用process.nextTick。

四、微任务

根据语言规格,Promise对象的回调函数,会进入异步任务里面的”微任务”(microtask)队列。

微任务队列追加在process.nextTick队列的后面,也属于本轮循环。所以,下面的代码总是先输出3,再输出4。

process.nextTick(() => console.log(3));Promise.resolve().then(() => console.log(4));// 3// 4

登录后复制

Node定时器详细解析

注意,只有前一个队列全部清空以后,才会执行下一个队列。

process.nextTick(() => console.log(1));Promise.resolve().then(() => console.log(2));process.nextTick(() => console.log(3));Promise.resolve().then(() => console.log(4));// 1// 3// 2// 4

登录后复制

上面代码中,全部process.nextTick的回调函数,执行都会早于Promise的。

至此,本轮循环的执行顺序就讲完了。

同步任务process.nextTick()微任务

登录后复制

五、事件循环的概念

下面开始介绍次轮循环的执行顺序,这就必须理解什么是事件循环(event loop)了。

首先,有些人以为,除了主线程,还存在一个单独的事件循环线程。不是这样的,只有一个主线程,事件循环是在主线程上完成的。

其次,Node 开始执行脚本时,会先进行事件循环的初始化,但是这时事件循环还没有开始,会先完成下面的事情。

同步任务

发出异步请求

规划定时器生效的时间

执行process.nextTick()等等

最后,上面这些事情都干完了,事件循环就正式开始了。

六、事件循环的六个阶段

事件循环会无限次地执行,一轮又一轮。只有异步任务的回调函数队列清空了,才会停止执行。

每一轮的事件循环,分成六个阶段。这些阶段会依次执行。

timersI/O callbacksidle, preparepollcheckclose callbacks

每个阶段都有一个先进先出的回调函数队列。只有一个阶段的回调函数队列清空了,该执行的回调函数都执行了,事件循环才会进入下一个阶段。

Node定时器详细解析

下面简单介绍一下每个阶段的含义,详细介绍可以看官方文档,也可以参考 libuv 的源码解读。

(1)timers

这个是定时器阶段,处理setTimeout()和setInterval()的回调函数。进入这个阶段后,主线程会检查一下当前时间,是否满足定时器的条件。如果满足就执行回调函数,否则就离开这个阶段。

(2)I/O callbacks

除了以下操作的回调函数,其他的回调函数都在这个阶段执行。

setTimeout()和setInterval()的回调函数

setImmediate()的回调函数

用于关闭请求的回调函数,比如socket.on(‘close’, …)

(3)idle, prepare

该阶段只供 libuv 内部调用,这里可以忽略。

(4)Poll

这个阶段是轮询时间,用于等待还未返回的 I/O 事件,比如服务器的回应、用户移动鼠标等等。

这个阶段的时间会比较长。如果没有其他异步任务要处理(比如到期的定时器),会一直停留在这个阶段,等待 I/O 请求返回结果。

(5)check

该阶段执行setImmediate()的回调函数。

(6)close callbacks

该阶段执行关闭请求的回调函数,比如socket.on(‘close’, …)。

七、事件循环的示例

下面是来自官方文档的一个示例。

const fs = require('fs');const timeoutScheduled = Date.now();// 异步任务一:100ms 后执行的定时器setTimeout(() => { const delay = Date.now() - timeoutScheduled; console.log(`${delay}ms`);}, 100);// 异步任务二:至少需要 200ms 的文件读取fs.readFile('test.js', () => { const startCallback = Date.now(); while (Date.now() - startCallback < 200) { // 什么也不做 }});

登录后复制

上面代码有两个异步任务,一个是 100ms 后执行的定时器,一个是至少需要 200ms 的文件读取。请问运行结果是什么?

Node定时器详细解析

脚本进入第一轮事件循环以后,没有到期的定时器,也没有已经可以执行的 I/O 回调函数,所以会进入 Poll 阶段,等待内核返回文件读取的结果。由于读取小文件一般不会超过 100ms,所以在定时器到期之前,Poll 阶段就会得到结果,因此就会继续往下执行。

第二轮事件循环,依然没有到期的定时器,但是已经有了可以执行的 I/O 回调函数,所以会进入 I/O callbacks 阶段,执行fs.readFile的回调函数。这个回调函数需要 200ms,也就是说,在它执行到一半的时候,100ms 的定时器就会到期。但是,必须等到这个回调函数执行完,才会离开这个阶段。

第三轮事件循环,已经有了到期的定时器,所以会在 timers 阶段执行定时器。最后输出结果大概是200多毫秒。

八、setTimeout 和 setImmediate

由于setTimeout在 timers 阶段执行,而setImmediate在 check 阶段执行。所以,setTimeout会早于setImmediate完成。

setTimeout(() => console.log(1));setImmediate(() => console.log(2));

登录后复制

上面代码应该先输出1,再输出2,但是实际执行的时候,结果却是不确定,有时还会先输出2,再输出1。

这是因为setTimeout的第二个参数默认为0。但是实际上,Node 做不到0毫秒,最少也需要1毫秒,根据官方文档,第二个参数的取值范围在1毫秒到2147483647毫秒之间。也就是说,setTimeout(f, 0)等同于setTimeout(f, 1)。

实际执行的时候,进入事件循环以后,有可能到了1毫秒,也可能还没到1毫秒,取决于系统当时的状况。如果没到1毫秒,那么 timers 阶段就会跳过,进入 check 阶段,先执行setImmediate的回调函数。

但是,下面的代码一定是先输出2,再输出1。

const fs = require('fs');fs.readFile('test.js', () => { setTimeout(() => console.log(1)); setImmediate(() => console.log(2));});

登录后复制

上面代码会先进入 I/O callbacks 阶段,然后是 check 阶段,最后才是 timers 阶段。因此,setImmediate才会早于setTimeout执行。

相关推荐:

javascript定时器实现进度条功能

JavaScript基于定时器实现进度条的实例

nodejs中使用HTTP分块响应和定时器示例代码

以上就是Node定时器详细解析的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2785748.html

(0)
上一篇 2025年3月8日 17:32:21
下一篇 2025年2月26日 02:07:24

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • node下如何使用UglifyJS压缩合并JS文件

    本文主要和大家分享一篇node下使用uglifyjs压缩合并js文件的方法,现在uglifyjs的最新版本为 2.8.13,主要功能为js的压缩和合并,下面直接进入教程: 安装: npm install uglify-js -g 登录后复制…

    编程技术 2025年3月8日
    200
  • 怎样让Vue Es6解析为Es5语法

    这次给大家带来怎样让vue es6解析为es5语法,让vue es6解析为es5语法的注意事项有哪些,只需要一段简单的代码就可以让vue es6解析为es5语法下面就是实战案例,一起来看一下。 entry: {app: [‘babel-po…

    编程技术 2025年3月8日
    200
  • node静态文件服务器实例详解

    本文主要和大家介绍了实战node静态文件服务器的示例,本文首先会列出它的功能然后再以代码的形式分享给大家,希望能帮助到大家。 支持功能: 读取静态文件 访问目录可以自动寻找下面的index.html文件, 如果没有index.html则列出…

    编程技术 2025年3月8日
    200
  • 深入JavaScript之定时器

    这次给大家带来深入javascript之定时器,使用javascript的定时器注意事项有哪些,下面就是实战案例,一起来看一下。 获取 年 月 日 星期 时 分 秒 var date = new Date() date.getFullYea…

    2025年3月8日
    200
  • node的文件批量重命名

    这次给大家带来node的文件批量重命名,node文件批量重命名的注意事项有哪些,下面就是实战案例,一起来看一下。 在一个实际需求中,需要对一批文件(如:文本、图片)进行重命名,按照数字编号。正好借此熟悉了一下node的fs文件操作,写了一个…

    2025年3月8日
    200
  • 怎样实现node连接mysql的方法

    这次给大家带来怎样实现node连接mysql的方法,实现node连接mysql的注意事项有哪些,下面就是实战案例,一起来看一下。 mysql基本命令 修改root密码123为1234 mysqladmin -u root -p 123 pa…

    编程技术 2025年3月8日
    200
  • JavaScript的定时器详解

    这次给大家带来javascript的定时器详解,使用javascript的定时器注意事项有哪些,下面就是实战案例,一起来看一下。 除了放置异步任务的事件,”任务队列”还可以放置定时事件,即指定某些代码在多少时间之后执…

    编程技术 2025年3月8日
    200
  • Es6 Generator的函数最全解析

    这次给大家带来Es6 Generator的函数最全解析,使用Es6 Generator函数的注意事项有哪些,下面就是实战案例,一起来看一下。 ECMAScript 6 (简称 ES6 )作为下一代 JavaScript 语言,将 JavaS…

    编程技术 2025年3月8日
    200
  • Node如何启动https服务器

    这次给大家带来node如何启动https服务器,node启动https服务器的注意事项有哪些,下面就是实战案例,一起来看一下。 首先你需要生成https证书,可以去付费的网站购买或者找一些免费的网站,可能会是key或者crt或者pem结尾的…

    编程技术 2025年3月8日
    200
  • js递归原理解析

    本文主要和大家讲述关于JS的函数递归,主要从“变量+函数”和“函数+变量”两个方面说明解释,希望能帮助到大家。 相对简单,直接上代码。 一、知识说明 function fun(){    // 自己调用自己,称为递归调用    fun();…

    编程技术 2025年3月8日
    200

发表回复

登录后才能评论