《Eventloop事件循环》

511 阅读4分钟

什么是事件循环?它不是一个真实存在的对象,而是一个概念,表示不同阶段的变换。类似于人生循环,生老病死,形成一个环。就是一个阶段跳到下一个阶段的过程。

一. nodejs里的eventloop

在nodejs里,事件循环是分阶段的: 我们主要关注:timers poll check 三个阶段。

每个阶段都有一个自己的任务队列。

timers 里保存setTimeout和setInterval

poll 阶段是停留,一直等,比如timers里有一个1秒后执行f1,那在poll阶段,如果后边没有任务了,就会一直停在poll,直到到了时间,就去timers里执行回调。

check 阶段里保存setImmediate (这是nodejs里的API)

还有一个问题,开始eventloop和执行JS代码哪个会先启动呢?大部分情况是什么都准备好了,再开始执行JS代码,此时eventloop应该处在poll阶段停留,等待。但是也有可能eventloop开启的很慢,等JS开始执行了,它才进入到timers阶段。所以这就造成了setTimeout和setImmediate 的顺序问题。

setTimeout(() => {
    console.log('setTimeout');   //f1
}, 0);
setImmediate(() => {
    console.log('setImmediate');  //f2
});

在node里运行,会先打印f1还是f2呢?结果是都有可能。正常来说应该是先打印f2,再f1。但是运行发现有时先f1再f2。这是为什么呢?

先说大部分情况:事件循环先开启了,这时JS还没执行。timers阶段没任务,就跳到poll阶段等待。这时JS代码执行了,遇到setTimeout,把f1放进timers;遇到setImmediate,把f2放进check。然后poll阶段看到后边的check里有事要做了,就跳到check阶段,执行f2。执行完以后,又回到timers,发现有了任务,而且也到时间了,就执行f1。

特殊情况:事件循环开启的比较慢,JS代码先执行了。遇到setTimeout,把f1放进timers。遇到setImmediate,把f2放进check。这时eventloop开启了,进入timers,看到马上执行f1,就直接执行了。然后跳到poll,看到check里有任务要执行了,就不等了,去check里执行f2。所以先f1,再f2

所以,不是说setImmediate里的一定会先执行

那怎么让f2一定先执行呢?用一个setTimeout包他俩包起来,把执行时间整体往后推,确保等事件循环启动好了,再把f1,f2放里边。

setTimeout(()=>{
	setTimeout(() => {
    console.log('setTimeout');   //f1
	}, 0);
	setImmediate(() => {
    console.log('setImmediate');  //f2
	});
},1000)

等1秒后,再setTimeout和setImmediate。这时eventloop肯定都准备好了,处在poll阶段等待,然后看到check里有任务,就先去执行f2。之后回到timers,执行f1。因为从poll开始,肯定是先经过check,再回到timers。这也就是为什么通常setImmediate会比setTimeout先执行。就是因为他们处于不同的阶段。

还有一个异步API叫 process.nextTick() ,它的顺序是什么样的呢?process.nextTick()是一个特殊的异步API,他不属于任何的Event Loop阶段。事实上Node在遇到这个API时,Event Loop根本就不会继续进行,会马上停下来执行process.nextTick(),这个执行完后才会继续Event Loop。不管Event Loop当前处于哪个阶段,nextTick都会在当前阶段被执行。

setTimeout(()=>{
	setTimeout(() => {
    console.log('setTimeout');   //f1
	}, 0);
	setImmediate(() => {
    console.log('setImmediate');  //f2
	});
    process.nextTick(() => {
      console.log('nextTick 1');   //f3
    });
},1000)

当前处于poll阶段。JS代码执行,遇到setTimeout,把f1放进timers里(只是放进去,并不会执行);遇到setImmediate,把f2放进check里。遇到了nextTick,就马上在当前阶段执行,当前还是poll阶段,所以先执行f3。然后进入check阶段,执行f2。回到timers,执行f1。

nextTick并不总是在poll阶段执行的,它在哪个阶段,就在哪个阶段执行它.

setTimeout(()=>{
	setTimeout(() => {
    	console.log('setTimeout');   //f1
        process.nextTick(() => {
      		console.log('nextTick 2');   //f4
    });
	}, 0);
	setImmediate(() => {
    	console.log('setImmediate');  //f2
	});
    process.nextTick(() => {
      console.log('nextTick 1');   //f3
    });
},1000)

顺序就是 3-2-1-4。f4是始终跟着f1的,在timers阶段被执行。简单来说,nextTick会在当前阶段结束时,跳到下一个阶段之前执行。处在一个中间转移的时间点上。

二. 浏览器里的eventloop

在浏览器里,事件循环只有两个阶段:宏任务,微任务

这就和setTimeout,Promise,async,await的顺序有关。