本文阐述了浏览器端和node端的js运行机制执行的过程,还进行了两者的运行机制比较,以及同步任务和异步任务的说明,两种异步任务的必要性,以及各自有哪些回调,部分回调的优先级。
首先js执行,会有一个函数执行栈(stack),一个任务队列(task queue),一个微任务队列(microtask queue),事件循环(event loop)。
附:
「同步任务」:在主线程上执行的任务,只有前一个任务执行完成后才能执行下一个任务。 「异步任务」:不进入主线程执行,而是进入到任务队列(task queue)中执行。
上段讲的是浏览器端的事件轮询,而node是多线程机制,由libuv库负责Node API的执行,将它分配给不同的线程,形成一个事件循环。
node中事件循环(event loop)大致分为六个部分。
主要的是timer定时器、poll轮询、check 检查三大部分。在此我们只做了解。
事件循环过程:
js的异步任务分为两种:宏任务、微任务。一个宏任务里面可以拥有多个微任务,在执行js代码块的时候才会去执行内部的微任务。
macrotask,也叫tasks。一些异步任务的回调会依次进入宏任务队列,等待后续背调用。
宏任务包括:
注意: 1、setTimeout延迟时间为0,与requestAnimationFrame比较:「requestAnimationFrame优先级大于setTimeout」。
2、setTimeout延迟时间为0,与setImmediate比较:「不确定」。
setTimeout(() => console.log('setTimeout'), 0)
setImmediate(()=>{
console.log('setImmediate');
})
解释: timer前的准备时间超过1ms,(loop到timer的时间大于1ms),则执行timer阶段(setTimeout)的回调函数。 timer前的准备时间小于1ms,则先执行check阶段(setTimeout)的回调函数,下次事件循环,再执行timer阶段的回调函数。
如果想要setImmediate先执行,可以使用fs文件包裹,确保在I/O回调阶段执行。这样时间循环,会先执行chack阶段,之后再执行timer阶段。
setTimeout(() => {
console.log(1)
})
setTimeout(() => {
console.log(2)
Promise.resolve().then(function () {
console.log('promise')
})
})
setTimeout(() => {
console.log(3)
})
microtask,也叫jobs。除宏任务外的一些异步回调会依次进入微任务队列,等待后续被调用。微任务包括:
注意: process.nextTick优先级高于Promise.then()。
在异步任务队列中,遵循先进先出的原则。此时,在众多异步任务中,如果存在优先级较高的任务需要优先执行,那么只有一个异步任务队列是无法满足的,此时就需要引入微任务队列,将优先级较高的任务放到微任务队列中。如果微任务队列非空,则执行微任务队列,否则执行宏任务队列。 如果只有一种异步任务,那么优先级高的异步任务无法优先执行。
async/await
本质上还是基于Promise
的一些封装
「async」函数在await之前的代码都是「同步」执行的,可以理解为await之前的代码属于new Promise时传入的代码,「await之后的所有代码都是在Promise.then中的回调」。