同步任务和异步任务
// test.js
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
process.nextTick(() => console.log(3));
Promise.resolve().then(() => console.log(4));
(() => console.log(5))();
// 5
// 3
// 4
// 1
// 2
上边这段代码中 (() => console.log(5))(); 为同步任务,所以最先执行,剩下的为异步任务,
其中
process.nextTick(() => console.log(3));
Promise.resolve().then(() => console.log(4));
总是先于定时器执行,可以理解为 **process.nextTick和promise在下一轮事件循环之前执行,而执行到定时器的时候会计算时间阀阈值,达到时间阈值之后,添加入事件循环队列,**所以 总是3,4 早于 1,2执行
总之 process.nextTick、promise是最先执行的异步任务
其次,process.nextTick早于promise先执行,Node 执行完所有同步任务,接下来就会执行process.nextTick的任务队列,promise任务追加在process.nextTick队列之后
所以以下代码总会先输出 3,再输出 4
process.nextTick(() => console.log(3));
Promise.resolve().then(() => console.log(4));
// 3
// 4
需要注意的是,只有上一个队列清空完毕之后,才会执行下一个队列
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
什么是event loop
js是单线程的,事件循环使nodejs将非阻塞的I/O操作转移到系统内核来执行(windows平台下的 IOCP, *nix为自定义的线程池),它们可以在后台执行多个操作,当其中一个操作完成的时候,通知nodejs,以便将回调添加到轮询队列以最终执行
当nodejs开始执行,处理输入的脚本,该脚本可能进行异步 api 调用、调度定时器或者调用 process.nextTock(),然后开始处理时间循环
下图展示了事件循环操作顺序的简化概览:
每个框都被称为事件循环机制的一个阶段,每个阶段都有一个先进先出的(FIFO)的回调队列,通常情况下,当事件循环进入给定的阶段,它将在该阶段执行队列中的回调,直到队列耗尽或者达到执行回调的最大数目,当队列耗尽或者达到执行回调的最大数目的时候,事件循环将进入到下一个阶段,依次类推
阶段概述
timers:在这个阶段执行setTimeout、setInterval的回调
**pending callbacks:**执行延迟到下一个循环迭代的I/O回调
**idle, prepare:**仅内部使用
**poll:**这个阶段是轮询阶段,用于检索新的I/O事件,等待还未返回结果的 I/O 事件,执行与I/O相关的回调队列,如果没有其它的异步任务要处理(比如到期的定时器),则会一直停留在这个阶段,
这里的回调,除了以下回调
· setTimeout 和 setInterval的回调
· setImmediate 的回调
· 用于关闭请求的回调,如:socket.on('close',...)
**check:**该阶段执行 setImmediate 的回调
**close callbacks:**该阶段执行关闭回调,比如: socket.on('close',...)
阶段的详细介绍
timers
计时器指定了一个阈值,在这个阈值之后可以执行一个提供的回调函数,注意阈值不是指执行的确切时间,计时器回调将在指定的时间过后尽快运行,执行其它操作被阻塞时会延迟执行
const fs = require('fs');
function someAsyncOperation(callback) {
// 假设这个读取文件的操作需要 95ms 才能完成
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);
someAsyncOperation(() => {
const startCallback = Date.now();
// 做一些其它操作 需要 10ms
while (Date.now() - startCallback < 10) {
// do nothing
}
});
1. 当事件循环执行到轮询阶段时(在轮询阶段等待),此时它有一个空队列(fs.readFile尚未完成),
2. 在等待的第 95ms 的时候,读取文件完成,并且将需要执行10ms的回调添加到轮询的回调队列,并执行,
3. 回调完成之后,事件循环查看到已经达到阈值的计时器,然后返回到计时器阶段执行计时器回调
4. 在此示例中将看到计划计时器,与执行计时器回调总为 105ms
pending callbacks
这个阶段会执行一些系统操作的回调,如 TCP 错误的类型,例如,如果 TCP 的 socket在尝试连接时收到的 'ECONNREFUSED',一些 *nix 系统希望等待报告错误,这将会在 pending callbacks 阶段的回调队列执行
poll
这个阶段主要做两件事情
1. 计算它应该阻塞多长时间,并轮询 I/O
2. 处理轮询队列中的回调
当事件循环进入 poll 阶段,并没有计时器任务时,将发生以下这两种情况之一:
1. 如果轮询回调队列不是空的,则事件循环将依次同步执行它们,
2. 如果轮询回调队列是空的,则将发生另外两种情况之一:
1. 如果安排了 setImmediate,则事件循环将结束轮询阶段,并继续进入check阶段,执行回调队列
2. 如果没有安排 setImmediate,则事件循环将继续等待回调脚本添加到当前阶段的队列
此外,只要轮询队列为空,事件循环将检查已经到达阈值的计时器,如果一个或者多个计时器准备好了,将回滚到计时器阶段来执行这些计时器回调队列
check
通常代码执行时,事件循环最终将达到轮询阶段,在轮询阶段它将等待传入的连接、请求等I/O事件,但是如果已经使用setImmediate安排了回调,并且轮训阶段回调队列为空,则它将进入检查阶段,而不是继续等待轮训事件,
close callbacks
如果一个 socket 或者 handle 突然关闭(例如:socket.destory()),则将在此阶段触发close事件
setImmediate和setTimeout
由于setTimeout 在timers 阶段执行,setImmediate 是在 check 阶段执行的,理论上来讲当 setTimeout 第二个参数即阈值设置为 0 的时候,setTimeout 会比 setImmdiate 先执行,即
setTimeout(() => console.log(1), 0);
setImmediate(() => console.log(2));
上述代码执行顺序为 1 , 2,但是 实际情况是不确定的,因为nodejs 是做不到 0ms的,当第二个参数大于2147483647或小于1时,将自动设置为1,非整数的将自动截取为整数
实际执行时,进入事件循环的时候如果到了 1ms,则会先执行 setTimeout,如果没有到 1ms 则会跳过 timers 阶段,进入 check 阶段,先执行 setImmediate,所以这受到当前进程的性能影响,
但是如果在 I/0 回调内调用这两个任务,则setImmediate 一定先于 setTimeout 执行
const fs = require('fs');
fs.readFile('test.js', () => {
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
});
上述代码会先进入 poll 阶段,然后进入 check 阶段,最后再进入 timers阶段
参考链接:
nodejs.org/en/docs/gui…
www.ruanyifeng.com/blog/2018/0…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!