avatar

目录
异步和 Event Loop
-

JS 同步与异步

同步

函数在返回的时候能够拿到执行结果

异步

函数返回的时候,暂时得不到结果,将来通过一定手段(例如回调函数)得到结果,例如 AJAX 操作,定时器,文件异步读取等

单线程

JavaScript 是一门语言,单线程还是多线程由运行环境决定;在 js 的运行环境浏览器或者 Node 中 js 的执行是单线程的

单线程是指 js 引擎中负责解析执行 js 代码的线程只有一个

一个浏览器通常由以下几个常驻的线程:

  1. 渲染引擎线程,负责页面的渲染
  2. js 引擎线程,负责 js 的解析和执行
  3. 定时触发器线程,处理 setInterval 和 setTimeout
  4. 事件触发线程,处理 DOM 事件
  5. 异步 http 请求线程,处理 http 请求

Event Loop

js 实现异步的核心就是事件循环( Event Loop )

chrome 中的 Event Loop

过程

同步任务直接在主线程上排队执行,异步任务被放到相应的任务队列中,任务下一步会被移到调用栈(call stack),然后主线程执行调用栈的任务。

两种异步任务

宏任务( macrotask ):一会执行,setTimeout , setInterval

微任务( microtask ):马上执行, promise.then(fn) ,注意当 promise 实例 resolve 的时候 fn 回调才会放到微任务中

  • 微任务优先宏任务执行
  • 宏任务是在队列中按顺序逐个执行,微任务是一次执行完一整个队列
  • 创建 Promise 实例时传入的函数是同步任务;new Promise(fn)

实例

javascript
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0)
})
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2')
})
},0)

// 输出结果: Promise1,setTimeout1,Promise2,setTimeout2

面试例题

javascript
async function async1(){
console.log(1)
await async2()
console.log(2)
}

async function async2(){
console.log(3)
}

async1()

new Promise(function(resolve){
console.log(4)
resolve()
}).then(function(){
console.log(5)
})
// 打印结果 1(同步) 3(同步) 4(同步) 2(微任务) 5(微任务)
// 注意 await 要转化为 promise 再判断

解析:

javascript
await async2()
console.log(2)
// 等同于
new Promise(async2).resolve(()=>{
console.log(2)
})

JavaScript同步和异步
深入理解js事件循环机制(浏览器篇)

Node.js 中的 Event Loop

启动

当 Node.js 启动时,会做这几件事:

  1. 初始化 event loop
  2. 开始执行脚本,这些脚本有可能会调用一些异步 API 、设定计时器或者调用 process.nextTick()
  3. 开始处理 event loop

事件循环

重要阶段解析

timers : 执行 setTimeout() 、setInterval() 的回调

poll (轮循) : 获取新的 I/O 事件, Node 会去检查有无已过期的 timer,如果有则把它的回调压入 timers 的任务队列中等待执行

check: 执行 setImmediate() 的回调

process.nextTick() 并不是 event loop 的一部分,不管 event loop 当前处于哪个阶段,nextTick 队列都是在当前阶段后就被执行了。

setImmediate() vs setTimeout(fn,0)

setImmediate() 的作用是在当前 poll 阶段结束后调用一个函数。

setTimeout() 的作用是在一段时间后调用一个函数。 即使 delay 设置为 0,最小也默认为4

直接运行时无法确定两者的执行顺序,

  • 如果 Node 启动时间比较长,进入事件循环时,setTimeout() 已经超时了,就会直接在 timers 调用
  • 如果 Node 启动时间比较短,进入 timers ,然后 poll ,然后在 check 调用了 setImmediate()
javascript
setTimeout(() => {
console.log('timeout');
}, 0);

setImmediate(() => {
console.log('immediate');
});

如果把上面代码放到 I/O 操作的回调里,setImmediate 的回调就总是优先于 setTimeout 的回调

因为在读取文件在 poll 阶段拿到回调,会先经过 check

javascript
const fs = require('fs');

fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
});

// 打印结果 immediate timeout

实例

javascript
setTimeout(()=>{
setImmediate(()=>{
console.log('immediate')
})
process.nextTick(
function(){
console.log('nextTick')
}
)
},10)

// 打印结果 nextTick immediate
// process.nextTick() 在timers阶段结束时立即执行
// setImmediate() 被放到下一次循环中的check执行

Event Loop、计时器、nextTick
深入理解js事件循环机制(Node.js篇)

文章作者: GRIT
文章链接: https://grit0821.github.io/Blog/2019/12/04/%E5%BC%82%E6%AD%A5%E5%92%8C%20Event-Loop/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Grit's World