问题
- 单线程如何实现异步
- EventLoop循环的过程
单线程和异步
js的任务分为同步和异步两种,它们的处理方式也不同,
同步任务是直接在主线程上排队执行,
异步任务则会被放到任务队列中,
若有多个任务(异步任务)则要在任务队列中排队等待,
任务队列类似一个缓冲区,任务下一步会被移到调用栈(callstack),然后主线程执行调用栈的任务。
EventLoop
根据第二部分的表述可以知道 同步任务和异步任务是分开的
而事件循环就是基于之上的
调用栈的任务执行完之后会去查看任务队列是否存在任务
若是存在则推到调用栈执行
执行完之后再去查看任务队列
基于这种机制形成的循环就叫 EventLoop
宏任务和微任务
从上一部分的表述知道了事件循环的基本原理
但事实上任务队列 并不只有一个
宏任务 包括
- 整体JS代码,
- 事件回调,
- XHR回调,
- 定时器(setTimeout, setInterval, setImmediate),
- IO操作,
- UI render
微任务 包括
- promise回调
- MutationObserver
- process.nextTick
- Object.observe(已废弃)
其中定时器 setImmediate(某些浏览器也有,非标准 )和process.nextTick是node独有
宏任务与微任务执行的机制(浏览器端)
基于宏任务与微任务又存在一个运行机制
- 检查macrotask队列是否为空,非空则到2,为空则到3
- 执行macrotask中的一个任务
- 继续检查microtask队列是否为空,若有则到4,否则到5
- 取出microtask中的任务执行,执行完成返回到步骤3
- 执行视图更新
nodeJS的差异
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
看这样一端代码 按照上诉的事件循环机制在浏览器端是
timer1
promise1
timer2
promise2
事实在浏览器端确实如此,可是在node中结果却大相径庭
timer1
timer2
promise1
promise2
造成这一切的原因是什么呢?
目前发现node14以上的版本 打印结果已经与浏览器表现一致 但是未在更新说明中找到这一块的描述
事实NodeJS的事件循环分为 六个阶段
- timer阶段
- I/O callback阶段
- idle, prepare 阶段
- poll 阶段
- check 阶段
- close callbacks 阶段
node 的初始化
- 初始化 node 环境。
- 执行输入代码。
- 执行 process.nextTick 回调。
- 执行 microtasks。
进入 event-loop
- 进入 timers 阶段
- 检查 timer 队列是否有到期的 timer 回调,如果有,将到期的 timer 回调按照 timerId 升序执行。
- 检查是否有 process.nextTick 任务,如果有,全部执行。
- 检查是否有microtask,如果有,全部执行。
- 退出该阶段。
- 进入IO callbacks阶段。
- 检查是否有 pending 的 I/O 回调。如果有,执行回调。如果没有,退出该阶段。
- 检查是否有 process.nextTick 任务,如果有,全部执行。
- 检查是否有microtask,如果有,全部执行。
- 退出该阶段。
- 进入 idle,prepare 阶段:
这两个阶段与我们编程关系不大,暂且按下不表。
- 进入 poll 阶段
首先检查是否存在尚未完成的回调,如果存在,那么分两种情况。
- 第一种情况:
- 如果有可用回调(可用回调包含到期的定时器还有一些IO事件等),执行所有可用回调。
- 检查是否有 process.nextTick 回调,如果有,全部执行。
- 检查是否有 microtaks,如果有,全部执行。
- 退出该阶段。
- 第二种情况:
-
如果没有可用回调。
- 检查是否有 immediate 回调,
- 如果有,退出 poll阶段。如果没有,阻塞在此阶段,等待新的事件通知。
- 如果不存在尚未完成的回调,退出poll阶段。
-
进入 check 阶段。
- 如果有immediate回调,则执行所有immediate回调。
- 检查是否有 process.nextTick 回调,如果有,全部执行。
- 检查是否有 microtaks,如果有,全部执行。
- 退出 check 阶段
- 进入 closing 阶段。
-
如果有immediate回调,则执行所有immediate回调。
-
检查是否有 process.nextTick 回调,如果有,全部执行。
-
检查是否有 microtaks,如果有,全部执行。
-
退出 closing 阶段
-
检查是否有活跃的 handles(定时器、IO等事件句柄)。