从event loop出发讨论Promise、setTimeout的执行顺序

4,565 阅读4分钟

Event Loop 这个概念相信大家或多或少都了解过,所谓温故而知新,so,今天,我们就从event loop出发,看看在事件的执行过程中,他都经历了些什么。

什么是event loop

event loop是js的事件执行机制,我们一般简称为事件循环(之所以称作事件循环,是因为它经常被用于类似如下的方式来实现)

while (queue.waitForMessage()) {
  			queue.processNextMessage();
			}

​ 如果当前没有任何消息queue.waitForMessage 会等待同步消息到达,当完成当前任务后,继续去查看有无需要执行的任务如果需要执行,就再次执行,如此循环,所以称之为事件循环。

同步和异步任务

​ 要了解异步线程我们首先应该明白它的用处,因为js的单线程特性,任务的执行顺序都是依次执行,而当我们在工作中遇到网络请求,前后端交互的时候,你的数据不会马上拿到,这需要时间,如果等拿到数据再执行下面的代码,这样如果多次请求就会发现加载速度极慢,这样显然不合理,这样就会出现很多次的暂停等待,所以这时候 需要执行异步任务,当我们发起请求时候,采用异步的方式,浏览器检测到其为异步时,就会开辟一个新的进程处理该函数,然后继续执行后面的任务,当完成了执行栈里的同步任务之后,再检测是否有异步任务需要执行,最后执行异步任务。

-同步任务进入主线程,按顺序从上而下依次执行,
-异步任务,进入`event table` ,注册回调函数 `callback` ,
 任务完成后,将`callback`移入`event queue`中等待主线程调用

异步任务分为微任务和宏任务

​ 在执行过程中,我们知道了同步任务会优先异步任务执行,那么在异步中呢,异步中同样包含微任务和宏任务,首先我们大概了解下微任务和宏任务,在js中:

  • 微任务(micor Task) :promise MutationObserver process.nextTick
  • 宏任务(macro Task):script settimeout setinterval setImmediate requestAnimationFrame

宏任务

#浏览器node
I/O
setTimeOut
setInterval
setImmediate×
requestAnimationFrame×

微任务

#浏览器node
process.nextTick×
MutationObserver×
Promise.then catch finally

这两种任务在不同环境下支持的各不同,今天我们主要看看在浏览器中,我们经常会遇到的有 promisesetTimeout 我们通过下面这段代码来看看:

    console.log(1)
    
    setTimeout(() => console.log(2), 0)
    
    new Promise((resolve, reject) => {
        console.log(3)
        resolve()
    }).then(() => {
        console.log(4)
    })

首先来分析下,这段代码中包含同步任务,包含异步的宏任务setTimeout,包含异步的微任务promise,这套题的答案是1.3.4.2 ,我们首先找到同步任务,1 3 是同步任务,然后执行异步任务,异步任务如果按顺序执行则是24 但是答案是4.2那么我们可以知道 promise的执行顺序优先于setTimeout所以由此可知,在异步任务中,微任务优先于宏任务执行,可以看看下图。

红线就是任务的执行顺序

黑线是任务的结构

看完这么多下面来完成下面这道题并加以分析:

console.log(1)
setTimeout(() => {
    console.log(2)
    new Promise((resolve, reject) => {
        console.log(3)
        resolve()
    }).then(() => {
        console.log(4)
    })
}, 0)
new Promise((resolve, reject) => {
    console.log(5)
    resolve()
}).then(() => {
    console.log(6)
})
setTimeout(() => {
    console.log(7)
    new Promise((resolve, reject) => {
        console.log(8)
        resolve()
    }).then(() => {
        console.log(9)
    })
}, 0)
console.log(10)

答案:1 , 5 , 10 , 6 , 2 , 3 , 4 , 7 , 8 , 9

​ 废话不多说直接解题

  • 进入主线程开始执行, 遇到 console.log(1), 输出 1
  • 遇到一个 setTimeout 宏任务, 将其回调函数推入 macro Taskevent queue 中,macro Taskevent queue 中记一个任务 setTimeout1
  • 然后碰到 promise 微任务, 直接执行 new Promise 输出 5, 并将 then 函数的回调函数推入 micro Taskevent queue 中, micro Taskevent queue 中记 一个 微任务 promise1
  • 又遇到了 setTimeout 宏任务, 同理,将其回调函数推入 macro Taskevent queue 中,macro Taskevent queue 中记一个任务 setTimeout2
  • 最后,执行 console.log(10), 输出 10

上一轮事件循环结束,我们发现,已经输出 1 5 10 了, 按照我们之前所说,这个时候,主线程会去检查 是否存在微任务,不难发现,这个时候的 event queue 是这个样子的

micro Task (微任务)macro Task(宏任务)
promise1setTimeout1
setTimeout2
主线程 ---> promis1 ---> settimeout1 ---> settimeou2 ---> 循环检查主线程任务栈是否还有任务