JS异步与EventLoop

2年前 (2022) 程序员胖胖胖虎阿
246 0 0

文章开始我想请问大家,什么是异步?为什么需要异步?我想很多人的回答会是setTimeout,Promise,async await等等;
但是其实异步是一种概念,setTimeout,Promise,async await只是执行异步的方法;

我们都知道JS是单线程语言,也就是说我们在JS代码中输入的代码会以任务的形式从前到后,从上到下依次进行,如果要进行下一个任务就需要上一个任务结束;如果一个任务花费事件过长就会导致页面卡顿,我们可以将这种运行模式叫做同步模式或者同步编程

如果我们可以使两个任务同时进行,而不是当一个任务结束之后进行下一个任务是否可以解决或者缓解这种卡顿现象?我们把这种思想叫做异步模式或者异步编程

为了更好的了解事件循环(Event Loop) 我们需要先了解一下什么是异步宏任务什么是异步微任务

异步宏任务与异步微任务

前面讲到JS是单线程语言,那么也就是说在页面中我们的任务事件最终也是在主线程中运行的,这些事件包括,页面的渲染,用户的交互,js的脚本执行,网络请求,文件读写等等; 为了协调这些任务有条不紊地在主线程上执行,页面进程引入了消息队列和事件循环机制,渲染进程内部会维护多个消息队列,比如延迟执行队列和普通的消息队列。然后主线程采用一个 for 循环,不断地从这些任务队列中取出任务并执行任务。我们把这些消息队列中的任务称为宏任务 ,宏任务分为同步宏任务与异步宏任务,在我们主线程执行栈当中进行的同步代码都可以称之为同步宏任务;
那么异步宏任务有哪些?
常见异步宏任务有:

  1. setTimeout
  2. setInterval
  3. 文件的读取
  4. dom事件(包括滚动,点击,鼠标移入等)

那么什么是微任务?通俗点讲就是需要异步执行的回调函数
常见的微任务有哪些?主要包括Promise的类方法与Promise的对象方法,async await中的await等;

什么是事件循环?

简单地说,对于 JS 运行中的任务,JS 有一套处理收集,排队,执行的特殊机制,我们把这套处理机制称为事件循环(Event Loop)

我们都知道浏览器执行js代码是在栈内存(stack) 当中执行的,假设现在有一段代码里面有同步代码与异步代码异步代码又分为宏任务与微任务,那么这段代码在浏览器当中是怎样执行的?执行顺序又是怎样的呢?

首先当执行一段代码的时候会开辟两段内存,分别为堆内存栈内存;然后它会创建两个队列,分别是任务监听队列WebAPI任务队列EventQueue 然后浏览器会优先在执行栈当中对同步代码(同步宏任务) 自上而下进行执行;如果在执行过程中遇到了异步任务会将异步任务放到 任务监听队列WebAPI 当中去进行监听,当监听到异步任务可以执行了,会将异步代码放到 任务队列EventQueue 当中等待;当同步代码执行完毕会将 任务队列EventQueue 当中的代码推到主线程当中按照顺序(先进先出原则)进行执行;这里需要说明的一点是任务队列EventQueue 分为 宏任务队列macro task queue微任务队列micro task queue 因为微任务队列的优先级比较高,所以会将微任务队列micro task queue 中的任务先推到主线程当中进行运行,然后再运行宏任务队列macro task queue 当中的任务;因为浏览器是单线程运行,并且同步代码,微任务代码,宏任务代码都在一个线程当中运行;所以如果在这个过程中无论是宏任务还是微任务代码有阻塞,都会影响执行栈的向下运行;这便是事件循环机制

所以执行顺序依次为:
同步任务->微任务->宏任务

JS异步与EventLoop

一道题彻底搞懂eventLoop

了解eventloop的运行机制之后我们来做一道题来巩固:

//阻塞方法,用于js阻塞
//delayTime单位毫秒
function wait(delayTime){
    let nowStamp = new Date().getTime()
    const endTime = nowStamp + delayTime
    while (true){
        if (nowStamp < endTime) {
            return
        }
        nowStamp = new  Date().getTime()
    }
}
console.log('sync1')
setTimeout(()=>{
    console.log('setTimeout1')
},100)
const p1 = new Promise(resolve=>{
    console.log('p1')
    resolve()
}).then(()=>{
    console.log('then1')
})
const p2 = new Promise(resolve => {
    console.log('p2')
    resolve()
}).then(()=>{
    setTimeout(()=>{
        console.log('setTimeout2')
    },100)
})
setTimeout(()=>{
    const p3 = new Promise(resolve => {
        console.log('p3')
    }).then(()=>{
        console.log('then3')
    })
},100)
wait(4000)
setTimeout(()=>{
    console.log('setTimeout4')
},5000)
setTimeout(()=>{
    console.log('setTimeout3')
},100)
console.log('sync2')

输出结果为:

`
sync1
p1
p2
sync2
then1
setTimeout1
p3
setTimeout3
setTimeout2
setTimeout4`

在这道题中包含了promise,setTimeout,主线程代码以及主线程阻塞;

我们自上而下解读:
我们运行主线程代码,首先会输出sync1,再往下

setTimeout(()=>{
    console.log('setTimeout1')
},800)

由于是异步宏任务,会被推到WepAPI当中进行监听,在100毫秒之后推入eventQueue当中的异步宏任务队列当中等待主线程与异步微任务队列执行完毕;

再往下

const p1 = new Promise(resolve=>{
    console.log('p1')
    resolve()
}).then(()=>{
    console.log('then1')
})

由于在promise当中的p1属于同步任务所以会被主线程输,而then1会被推到异步微任务对列当中等待主线程执行完毕;

const p2 = new Promise(resolve => {
    console.log('p2')
    resolve()
}).then(()=>{
    setTimeout(()=>{
        console.log('setTimeout2')
    },100)
})

同上,会先执行主线程代码输出p2,再将

setTimeout(()=>{
        console.log('setTimeout2')
  },100)

推到微任务队列

再往下

setTimeout(()=>{
    const p3 = new Promise(resolve => {
        console.log('p3')
    })
},100)

宏任务,推送到WebAPI并在100毫秒之后推入异步宏任务队列

wait(4000)

阻塞4000毫秒,同步代码不会向下执行,在此时setTimeout1已经被推入宏任务执行队列

setTimeout(()=>{
    console.log('setTimeout4')
},5000)

执行完阻塞之后进入WebAPI并在5000毫秒之后进入异步宏任务队列;

setTimeout(()=>{
    console.log('setTimeout3')
},100)

执行完阻塞之后进入WebAPI并在100毫秒之后进入异步宏任务队列;

console.log('sync2')

同步代码,直接输出

此时我们直接输出的同步代码有

sync1
p1
p2
sync2

他们将按顺序在浏览器输出

在微任务队列中的代码有
then1
以及P2 then方法中的宏任务

setTimeout(()=>{
    console.log('setTimeout2')
},100)

在到这里的时候会将setTimeout2推入WebApi并在100毫秒之后推入异步宏任务队列

至此异步微任务输出完毕
此时的输出结果为

sync1
p1
p2
sync2
then1

再然后我们会去执行异步宏任务队列当中的任务;
因为webApi监听推入的时间不同,此时我们在异步宏任务队列当中的顺序依次为

setTimeout1
p3
setTimeout3
setTimeout2
setTimeout4
并依次输出,最终结果为
`
sync1
p1
p2
sync2
then1
setTimeout1
p3
setTimeout3
setTimeout2
setTimeout4`

版权声明:程序员胖胖胖虎阿 发表于 2022年11月1日 下午5:56。
转载请注明:JS异步与EventLoop | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...