学习一下 JS 和 Node 的EventLoop

113 阅读3分钟

1、JS EventLoop 事件循环机制

执行栈:存储函数调用的栈结构,遵循先进后出的原则。

image

不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)。在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task。

微任务包括 process.nextTick ,promise ,MutationObserver,其中 process.nextTick 为 Node 独有。

宏任务包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering。

Event Loop 执行顺序如下所示:

先执行宏任务,宏任务执行完之后会去查看延迟队列里面的任务,如果延迟任务里面的时间过期了就将这个延迟任务放在宏任务,然后会接着执行微任务。

  1. 首先执行同步代码,这属于宏任务
  2. 当执行完所有同步代码后,执行栈为空,查询是否有异步代码(延迟队列里)需要执行
  3. 执行所有微任务
  4. 当执行完所有微任务后,如有必要会渲染页面
  5. 然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是 setTimeout 中的回调函数
console.log('script start')

async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end')
}
async1()

setTimeout(function() {
  console.log('setTimeout')
}, 0)

new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })

console.log('script end')
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout
注意:新的浏览器中不是如上打印的,因为 await 变快了
// script start => async2 end => Promise => script end => async1 end =>  promise1 => promise2 => setTimeout

2、Node EventLoop 事件循环机制

  1. Node.js采用V8作为js的解析引擎,而I/O处理方面使用了自己设计的libuv
  2. libuv是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的API
  3. 事件循环机制也是它里面的实现
    • V8引擎解析JavaScript脚本并调用Node API
    • libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎
    • V8引擎再将结果返回给用户

libuv

  1. 同步执行全局的脚本
  2. 执行所有的微任务,先执行nextTick中的所有的任务,再执行其它微任务
  3. 开始执行宏任务,共有6个阶段,从第1个阶段开始,会执行每一个阶段所有的宏任务

截屏2022-03-11 上午9.09.07.png

截屏2022-03-11 上午9.13.54.png

总的来说:

  1. 先执行全局的同步任务,
  2. 执行完同步任务之后开始执行微任务的回调【在微任务队列里,先执行nextTick队列,然后在执行promise的回调】,
  3. 微任务队列执行完了之后,执行timersetTimeout\setInterval)的回调
  4. 然后执行IO事件队列(执行除了 close事件的callbacks、被timers设定的callbacks、setImmediate()设定的callbacks 这些之外的callbacks
  5. 然后进入到idel,prepare阶段(但这进node内部使用,可忽略)
  6. 然后进入poll阶段(在这里会获取新的IO事件,适当的条件下node将堵塞在这里)
  7. 然后进入check阶段(执行setImmediate()的回调)
  8. 然后进入关闭事件close callbacks 阶段,执行socket.on('close',...)这些callback

联系上面的总结,自己写下这段代码的结果:

console.log(1); 
setTimeout(() => { 
	console.log(2); 
	process.nextTick(() => { 
		console.log(3); 
	}); 
	new Promise((resolve) => { 
		console.log(4); 
		resolve(); 
	}).then(() => { 
		console.log(5); 
	}); 
}); 
new Promise((resolve) => { 
	console.log(7);
	resolve(); 
}).then(() => { 
	console.log(8); 
}); 
process.nextTick(() => { 
	console.log(6); 
}); 
setTimeout(() => { 
	console.log(9);
	process.nextTick(() => { 
		console.log(10); 
	}); 
	new Promise((resolve) => { 
		console.log(11); 
		resolve(); 
	}).then(() => { 
		console.log(12); 
	}); 
});
176824911310512
console.log(1);
setImmediate(function () {
    console.log('immediate');
});
setTimeout(() => {
    console.log(2);
    process.nextTick(() => {
        console.log(3);
    });
    new Promise((resolve) => {
        console.log(4);
        resolve();
    }).then(() => {
        console.log(5);
    });
});

process.nextTick(() => {
    console.log('nextTick1');
    Promise.resolve().then(() => console.log('promise4'));
    process.nextTick(() => {
        console.log('nextTick2');
        Promise.resolve().then(() => console.log('promise5'));
        process.nextTick(() => {
            console.log('nextTick3')
            process.nextTick(() => {
                console.log('nextTick4')
            })
        })
    })
})
new Promise((resolve) => {
    console.log(7);
    resolve();
}).then(() => {
    console.log(8);
});
process.nextTick(() => {
    console.log(6);
});
setTimeout(() => {
    console.log(9);
    process.nextTick(() => {
        console.log(10);
    });
    new Promise((resolve) => {
        console.log(11);
        resolve();
    }).then(() => {
        console.log(12);
    });
});

17、nextTick1、6、nextTick2、nextTick3、nextTick4、8、promise4、promise5、24911310512、immediate

这些是自己的学习总结,如果有错误的地方请指出,谢谢!