1. JS中为什么要有事件循环
由于JS是单线程的,一些异步问题通常交由别的线程来处理,处理之后将结果加入事件队列,当JS线程代码执行完,会去事件队列中取事件,到JS线程继续执行,直到事件队列被清空:
JS线程——》其他线程——》事件队列
这三者构成了闭环,形成了事件循环
2. 浏览器中的事件循环
任务队列:
- 宏任务队列:setTimeout; setInterval; ajax; dom事件点击等
- 微任务队列:Promise.then; queueMicrotask
优先级:
- 微任务队列>宏任务队列
- 同一个队列内部:先进先出
注意:async函数会像普通函数一样正常执行,如果函数里面有await关键字,那么await后面表达式返回的是一个Promise对象,Promise.then是微任务
题目1
async-await promise.then queueMicrotask setTimeout
async function async1 () {
console.log('async1 start')
await async2();
console.log('async1 end');
}
async function async2 () {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout2')
}, 10)
setTimeout(function () {
console.log('setTimeout1')
}, 0)
async1();
new Promise (function (resolve) {
console.log('promise1')
resolve();
}).then (function () {
console.log('promise2')
})
queueMicrotask(()=>{ console.log('queueMicrotask') })
console.log('script end')
/**
* script start
* async1 start
* async2
* promise1
* script end
* async1 end
* promise2
* queueMicrotask
* setTimeout1
* setTimeout1
*/
- await后面表达式属于上一个代码块,表达式内容会被立即执行,返回promise.then会加入微任务队列
- setTimeout( ()=>{},10):回调函数10ms后才被加入宏任务队列
题目2
new Promise((resolve) => {
resolve();
})
.then(() => {
new Promise((resolve) => {
console.log(000);
resolve();
})
.then(() => {
console.log(111);
})
.then(() => {
console.log(222);
});
// return undefined;
})
.then(() => {
console.log(333);
});
/**
* 000
* 111
* 333
* 222
*/
- 执行完console.log(000)之后,就将之后的then函数塞入微任务队列
- 然后执行return undefined;
- 然后将console.log(333)的then塞入微任务队列
- 这时取出console.log(111)执行完后,将console.log(222)加入任务队列的队尾巴
- 所以333在222之前输出
3. Node中的事件循环
浏览器中的事件循环是根据HTML5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node中是由libuv
实现的,libuv是一个多平台的专注于异步IO的库,主要维护了一个EventLoop和worker threads(线程池)
事件循环像是一个桥梁,是连接着应用程序的JavaScript和系统调用之间的通道
- 无论是文件IO、数据库、网络IO、定时器、子进程,在完成对应的操作后,都会将对应的结果和回调函数放到事件循环(任务队列)中;
- 事件循环会不断的从任务队列中取出对应的事件(回调函数)来执行;
Node中的事件循环
- 定时器(Timers):执行setTimeout() 和 setInterval() 的回调函数。
- 待定回调(Pending Callback):对某些系统操作(如TCP错误类型)执行回调
- idle, prepare:仅系统内部使用。
- 轮询(Poll):检索新的 I/O 事件;执行与 I/O 相关的回调;
- 检测(check):setImmediate() 回调函数在这里执行。
- 关闭的回调函数:一些关闭的回调函数,如:socket.on(‘close’, …)
Node中任务队列
微任务队列
- next tick queue:process.nextTick;
- other queue:Promise的then回调、queueMicrotask;
宏任务队列:
- timer queue:setTimeout、setInterval;
- poll queue:IO事件;
- check queue:setImmediate;
- close queue:close事件
优先级:
- 微任务>宏任务
- 微任务中:process.nextTick队列 > 其他队列
- 宏任务中:setTimeout,setInterval队列 > IO队列 >setImmediate队列>close队列
- 每个队列中,先进先出
与浏览器区别
- 多了方法: procress.nextTick, setImmediate
- 微任务中有两种队列,宏任务中有四种队列,注意队列的优先级关系
- Node中事件循环,不仅包括宏任务,还有一些其他pending callbak和idle,prepare等
题目1
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
// 300ms后加入事件队列
setTimeout(function () {
console.log('setTimeout2')
}, 300)
setImmediate(() => console.log('setImmediate'));
setTimeout(function () {
console.log('setTimeout0')
}, 0)
process.nextTick(() => console.log('nextTick1'));
async1();
process.nextTick(() => console.log('nextTick2'));
new Promise(function (resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function () {
console.log('promise3')
})
console.log('script end')
/**
* script start
* async1 start
* async2
* promise1
* promise2
* script end
* nextTick1
* nextTick2
* async1 end
* promise3
* setTimeout0
* setImmediate
* setTimeout2
*/
- process.nextTick是先于promise.then执行
- resolve()将then的回调函数加入微任务队列中,之后的语句console.log(‘promise2’)依然会立即执行
- setTimeout队列的优先级高于setImmediate,如果前者队列中有值,会先清空