node中的事件环(Event Loop)

381 阅读3分钟


这里的一张图是js中事件执行的顺序图,从图中我们可以看到我们写的一段代码,运行的时候首先会进入执行栈,从上往下执行。

如:

console.log(1);
new Promise(function(resolve, reject) {
    console.log(2);
})//1,2

上面的同步任务的情况,当任务中有异步任务时,在执行栈中异步任务会进入Event Table并注册回调函数放到任务队列中。当主线程中的同步任务执行完后,读取任务队列中的异步任务,放到主线程中执行。

如:

console.log(1);
setTimeout(function() {
    console.log(2);
}, 0);
setTimeout(function() {
    console.log(3);
}, 0);
//1,2,3

像这样任务队列不断地取出放到主线程中执行,重复以上过程就是事件环(Event Loop)。


上面我们说了,异步代码会进入任务队列,但是任务队列中又分为宏任务队列和微任务队列。

宏任务:

timers(计时器)执行setTimeout以及setInterval回调

poll(轮询)执行poll中的i/o队列检查定时器是否到时

check(检查)存放setImmediate回调

微任务:

process.nextTick

Promise

代码放到任务队列里时,会区分是放到宏任务队列还是微任务队列,主线程任务执行完任务后会首先执行完微任务队列里的任务,然后清空微任务队列,再执行宏任务队列里的任务。

如:

setTimeout(function() {
    console.log(1);
}, 0);
process.nextTick(function() {
    console.log(2);
});
setTimeout(function() {
    console.log(3);
}, 0);
//2,1,3

当宏任务里面有微任务时,会先把宏任务队列执行完,切换宏任务类型之前先执行完微任务,清空微任务队列

如timer这个队列,执行宏任务没有切换宏任务类型:

setTimeout(function() {
    console.log(1);
    process.nextTick(function() {
        console.log(2);
    });
}, 0);

setTimeout(function() {
    console.log(3);
    process.nextTick(function() {
        console.log(4);
    });
}, 0);
//1,3,2,4

如timer和poll这个队列,执行宏任务有切换宏任务类型:

let fs = require("fs")
fs.readFile("1.txt",function(){
    console.log(1);
    process.nextTick(function() {
        console.log(2);
    });
})
setTimeout(function() {
    console.log(3);
    process.nextTick(function() {
        console.log(4);
    });
}, 0);
//3,4,1,2

当宏任务为不同的类型时,宏任务执行顺序为timers -- poll -- check,但是还是会看谁先执行完

如timers在check前面:

setTimeout(function() {
    console.log(1);
}, 1000);
setImmediate(function(){
    console.log(2)
});
//2,1
//因为check执行的时间更短

如timers在check前面,但是timer执行的时间不确定:

setTimeout(function() {
    console.log(1);
}, 0);
setImmediate(function(){
    console.log(2)
});
//2,1或者1,2

宏任务里面有宏任务时,里面的宏任务会在当前的宏任务下面比较

如poll中嵌套timers和check,代码执行完poll后会先走poll顺序下的check,然后才执行timers而不是上面那样谁的时间短执行谁:

let fs = require("fs")
fs.readFile("1.txt",function(){
    setTimeout(function() {
        console.log(1);
    }, 0);
    setImmediate(function(){
        console.log(2)
    })
})
//2,1

微任务中由于process.nextTick比promise中的then快所以同一个微任务队列中process.nextTick先执行

new Promise(function(resolve, reject) {
    resolve()
}).then(function(){
    console.log(2);
})
process.nextTick(function() {
    console.log(1);
})
//1,2

微任务中有宏任务时,微任务宏任务执行顺序和上面的一样

如下宏任务在微任务前执行是因为微任务执行的条件是宏任务类型切换时触发,下面的宏任务都是相同类型的所以没有触发微任务

setTimeout(function() {
    console.log(1);
    process.nextTick(function() {
        console.log(2);
    });
}, 0);
process.nextTick(function() {
    console.log(3);
    setTimeout(function() {
        console.log(4);
    }, 0);
})//3,1,4,2

最后总结一下:

宏任务:timers -- poll -- check 这个任务执行顺序和这个任务执行的时间综合考虑

微任务:process.nextTick --promise

执行微任务的条件是宏任务类型切换时,或者主线程中同步任务执行完微任务队列还有微任务时