node.js事件循环详解
谁先执行?
global里的将来执行的函数
setInterval(callback, delay[, ...args])
- callback 当定时器到点时要调用的函数。
- delay 调用 callback 之前要等待的毫秒数。
- …args 当调用 callback 时要传入的可选参数。
预定每隔 delay 毫秒重复执行的 callback。 返回一个用于 clearInterval() 的 Timeout。
当 delay 大于 2147483647 或小于 1 时,delay 会被设为 1。
如果 callback 不是一个函数,则抛出 TypeError。
setTimeout(callback, delay[, ...args])
- callback 当定时器到点时要调用的函数。
- delay 调用 callback 之前要等待的毫秒数。
- …args 当调用 callback 时要传入的可选参数。
预定在 delay 毫秒之后执行的单次 callback。 返回一个用于 clearTimeout() 的 Timeout。
callback 可能不会精确地在 delay 毫秒被调用。 Node.js 不能保证回调被触发的确切时间,也不能保证它们的顺序。 回调会在尽可能接近所指定的时间上调用。
注意:当 delay 大于 2147483647 或小于 1 时,delay 会被设为 1。
如果 callback 不是一个函数,则抛出 TypeError。
- 上面这两个函数和js里的原生函数用法类似,不做多讲:
util.promisify()
让一个遵循异常优先的回调风格的函数, 即 (err, value) => … 回调函数是最后一个参数, 返回一个返回值是一个 promise 版本的函数,即可以在回调结束时通过链式反应调用后续的then()
或catch()
等。
const util = require('util');
const setTimeoutPromise = util.promisify(setTimeout);
setTimeoutPromise(40, 'foobar').then((value) => {
// value === 'foobar' (passing values is optional)
// This is executed after about 40 milliseconds.
});
setImmediate(callback[, ...args])
- callback 在 Node.js 事件循环的当前回合结束时要调用的函数 。
- …args 当调用 callback 时要传入的可选参数。
预定立即执行的 callback,它是在 I/O 事件的回调之后被触发。 返回一个用于 clearImmediate() 的 Immediate。
当多次调用 setImmediate() 时,callback 函数会按照它们被创建的顺序依次执行。 每次事件循环迭代都会处理整个回调队列。
如果一个立即定时器是被一个正在执行的回调排入队列的,则该定时器直到下一次事件循环迭代才会被触发。
如果 callback 不是一个函数,则抛出 TypeError。
- 这个看起来像是新的api是怎么用的呢?我们先看一下它的机制:
我的文件名为index.js
setImmediate(()=>{
console.log('====================================');
console.log(1);
console.log('====================================');
})
console.log(2);
这个api是在nodejs的事件循环结束后立即调用!!!
同步执行的Promise,异步执行的then
- Promis对象接收resolve值为1,并触发后续then方法,再看谁先执行:
Promise.resolve(1).then(res =>{
console.log(res);
})
console.log(2);
Promise是同步,会直接生成一个状态为成功的promise对象,继续向后执行,但是then是异步的,所以会把后面的同步函数方法等执行后再执行then里的代码。
process进程
- process也是异步的:
process.nextTick(() =>{
console.log(1);
})
console.log(2);
到底谁先执行?
我们都多加几个试试:
- process.nextTick:
process.nextTick(() =>{
console.log("nextTick 1");
})
process.nextTick(() => {
console.log("nextTick 2");
})
console.log(2);
- 再加个setImmediate:
setImmediate(() => {
console.log("setImmediate 1")
})
process.nextTick(() =>{
console.log("nextTick 1");
})
process.nextTick(() => {
console.log("nextTick 2");
})
console.log(2);
- 我们再套个process呢?
setImmediate(() => {
console.log("setImmediate 1")
})
process.nextTick(() =>{
console.log("nextTick 1");
})
process.nextTick(() => {
console.log("nextTick 2");
})
setImmediate(() => {
console.log("setImmediate 2")
process.nextTick(() => {
console.log("nextTick 3");
})
})
console.log(2);
乱????
setImmediate(() => {
console.log("1")
})
process.nextTick(() =>{
console.log("2");
})
process.nextTick(() => {
console.log("3");
})
setImmediate(() => {
console.log("4")
process.nextTick(() => {
console.log("5");
})
})
console.log(6);
//6 2 3 1 4 5
- 没毛病,再加个settimeout:
setImmediate(() => {
console.log("1")
})
process.nextTick(() =>{
console.log("2");
})
process.nextTick(() => {
console.log("3");
})
setImmediate(() => {
console.log("4")
process.nextTick(() => {
console.log("5");
})
})
setTimeout(() => {
console.log(7);
}, 0);
console.log(6);
事件循环,同步和异步
因为nodejs由事件驱动,它会将所有的事件通过一个对象包装起来,通过循环机制,将事件挨个儿执行,重点就在包装这里!
- 你可以理解为nodejs有个无限循环的查找函数,不停的将事件推入一个队列,第一次循环将全局事件依照优先级推入队列,第二次循环将事件生成的事件推入队列,依次执行下去!
如何去理解js里的代码都是同步,而真正异步的是回调的触发行为!引发异步其实是异步回调函数!
macro-task:script(脚本代码)>>>>>settimeout/setinterval>>>>>setImmediate>>>>>I/O
micro-task:process>>>>>Promise
- 为了更清楚的认知到事件循环,我们重新整理代码作为对比:
setImmediate(() => {
console.log("1")
})
process.nextTick(() =>{
console.log("2");
})
process.nextTick(() => {
console.log("3");
})
setImmediate(() => {
console.log("4")
process.nextTick(() => {
console.log("5");
})
})
setTimeout(() => {
console.log(6);
process.nextTick(() => {
console.log("7");
})
setImmediate(() => {
console.log("8")
})
}, 0);
setTimeout(() => {
console.log(9);
Promise.resolve(10).then(res => {
console.log(res);
})
}, 0);
Promise.resolve(11).then(res=>{
console.log(res);
})
setTimeout(() => {
console.log(12);
}, 0);
console.log(13);
setInterval(() => {
console.log(14);
}, 0)//我用Ctrl + C打断持续执行
- nodejs里的包装函数的伪代码:
macro-task:script(脚本代码)>>>>>settimeout/setinterval>>>>>setImmediate>>>>>I/O
micro-task:process>>>>>Promise
while(代码还没有结束的监听,没有销毁就返回true){
第一步:查询整体脚本代码,将事件流推入队列;
macro:"script(13)">>>setTimeout(6 9 12)、setInterval(14)>>>setImmediate(1 4)
micro:process(2 3)>>>Promise(11)
结果打印:13
第二步:执行同步代码,process(),Promise();
macro:setTimeout(6 9 12)、setInterval(14)>>>setImmediate(1 4)
micro:"process(2 3)>>>Promise(11)"
结果打印:2 3 11
第三步:执行异步代码,setTimeoute()/setInterval(),将新的事件流推入队列;
macro:"setTimeout(6 9 12)、setInterval(14)" >>>setImmediate(1 4 8)
micro:++process(7)>>>Promise(10)
结果打印:6 9 12 14
第四步:执行同步代码,process(),Promise();
macro:setImmediate(1 4 8)
micro:"process(7)>>>Promise(10)"
结果打印:7 10
第五步:执行异步代码,setImmediate(),将新的事件推入队列;
macro:"setImmediate(1 4 8)"
micro:++process(5)
打印结果:1 4 8
第六步:执行同步代码,process();
macro:null
micro:"process(5)"
打印结果:5
第七步:监听事件队列状态
macro:null
micro:null
程序结束 return
//因而interval是持续循环的函数,所以还会执行,但并不是重新挂载的事件
}
可以看到,每执行一次异步代码都会把同步代码的队列进行执行清空,结果变得显而易见!