彻底弄懂 JavaScript 异步任务处理原理

目录

1.单线程

什么是单线程?

2.同步和异步

同步

异步

3.事件循环(EventLoop)

1.事件循环的基本概念

2.微任务/宏任务

3.宏任务和微任务的执行顺序

4.常见的面试题


1.单线程

首先我们需要明白JS是单线程的,这是为了降低程序复杂性,但同时为了多个事件能同时被处理,JS提供了异步的处理方式(其实JS本身是没有异步这一说法的,都是由执行环境所提供的)

什么是单线程?

就行马路的单向通行道一样,一排车只能一辆一辆的排队前行通过。

2.同步和异步

程序同一时间只做一件事! 场景如下:

同步

同步阻塞的概念指的是A调用B,B执行完获得结果返回给A,A在等待B执行过程中是停滞的状态,直到拿到结果才会往下执行

异步

异步与同步一样,其实也是一种消息通知机制,引用上面的比方,A调用B,无需等待B的结果,A继续往下执行,的同时B也在执行,B通过状态、通知等方式来通知A或者走回调来处理结果,做一件事的时候不用等待事情的结果,继续往下走,等有了结果再来通知我

彻底弄懂 JavaScript 异步任务处理原理_第1张图片

3.事件循环(EventLoop)

1.事件循环的基本概念

Tips:在js执行同步任务和异步任务的过程中,在主线程执行栈中的同步任务执行完毕就会,读取任务队列中的回调函数,将回调函数放到主线程执行栈中执行,执行完毕又会继续读取任务队列中的回调函数,放到主线程执行栈中执行,如此往复的过程称为事件循环(EventLoop);

彻底弄懂 JavaScript 异步任务处理原理_第2张图片

彻底弄懂 JavaScript 异步任务处理原理_第3张图片

2.微任务/宏任务

Tips:以上内容已经讲解了同步任务和异步任务的概念,javascript将异步任务又进一步划分成了宏任务和微任务;

常见的宏任务有:Ajax请求、文件操作、setTimeout、setInterval、Dom操作、

常见的微任务: Promise的then、catch、finally方法,Process.nextTick,mutationObserver;

彻底弄懂 JavaScript 异步任务处理原理_第4张图片

3.宏任务和微任务的执行顺序

在每一个宏任务执行完毕后就会去查找是否存在微任务,如果微任务存在就将微任务执行完毕再去执行下一个宏任务,以此往复,直到所有的任务执行结束

彻底弄懂 JavaScript 异步任务处理原理_第5张图片

4.常见的面试题

经典面试题
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('setTimeout')
},0)  
async1();
new Promise(function(resolve){
    console.log('promise1')
    resolve();
}).then(function(){
    console.log('promise2')
})
console.log('script end')

⛳️ 分析过程

1.定义一个异步函数 async1

2.定义一个异步函数 async2

3.打印 ‘script start’ // *1

4.定义一个定时器(宏任务,优先级低于微任务),在0ms 之后输出

5.执行异步函数 async1

  • 打印 'async1 start' // *2
  • 遇到await 表达式,执行 await 后面的 async2 //关键的地方,await后面的代码会阻塞 ,async2执行完毕后,后面的代码类似于传入then()中的回调
  • 打印 'async2' // *3
  • 返回一个 Promise,跳出 async1 函数体

6.执行 new Promise 里的语句

  • 打印 ‘promise1‘ // *
  • resolve() , 返回一个 Promise 对象,把这个 Promise 压进队列里

7.打印 ’script end’ // *

8.同步栈执行完毕

9.回到 async1 的函数体,async2 函数没有返回 Promise,所以把要等async2 的值 resolve,把 Promise 压进队列

10.执行 new Promise 后面的 .then,打印 ’promise2‘ // *6

11.回到 async1 的函数体,await 返回 Promise.resolve() ,然后打印后面的 ’async1 end‘ // *7

12.最后执行定时器(宏任务) setTimeout,打印 ’setTimeout‘ // *8

⛳️ 答案:

  • script start
  • async1 start
  • async2
  • promise1
  • script end
  • promise2
  • async1 end
  • setTimeout

async2改造一下

async function async1(){
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
function async2(){ // 去掉了 async 关键字
    console.log('async2');
}
console.log('script start')
setTimeout(function(){
    console.log('setTimeout')
},0)  
async1();
new Promise(function(resolve){
    console.log('promise1')
    resolve();
}).then(function(){
    console.log('promise2')
})
console.log('script end')

⛳️ 后续结论

  • script start
  • async1 start
  • async2
  • promise1
  • script end
  • async1 end
  • promise2
  • setTimeout

console.log("script start")
    new Promise((resolve) => {
            console.log("promise1")
            resolve('微任务1')
        })
        .then((data) => { //第一个Promise加入微任务列表
            console.log(data)
        })
    async function fun1() {
        console.log("fun1")
        let data = await fun2() //关键的地方,await后面的代码会阻塞 ,fun2执行完毕后,后面的代码类似于传入then()中的回调
        console.log(data)
        console.log("await堵塞")
    }
 
    setTimeout(() => {
        console.log("setTimeout 宏任务") //一个宏任务,加入宏任务队列
    }, 0)
    async function fun2() {
        console.log("fun2")
        // return "no promise"
        return new Promise((resolve, reject) => {
            resolve("fun2 微任务")
        })
 
        // return "await"
 
    }
    fun1() //代码开始执行
    new Promise((resolve) => {
            console.log("promise2")
            resolve("微任务2")
        })
        .then((data) => { //promise状态已经fulfilled,直接加入微任务队列
            console.log(data)
        })
 
 
    console.log("同步end") // 同步代码结束

⛳️ 上面代码,执行结果(这种情况是: await后面的异步函数return的是一个Promise)

  1. script start
  2. promise1
  3. fun1
  4. fun2
  5. promise2
  6. 同步end
  7. 微任务1
  8. 微任务2
  9. fun2 微任务
  10. await堵塞
  11. setTimeout 宏任务
console.log("script start")
    new Promise((resolve) => {
            console.log("promise1")
            resolve('微任务1')
        })
        .then((data) => { //第一个Promise加入微任务列表
            console.log(data)
        })
    async function fun1() {
        console.log("fun1")
        let data = await fun2() //关键的地方,await后面的代码会阻塞 ,fun2执行完毕后,后面的代码类似于传入then()中的回调
        console.log(data)
        console.log("await堵塞")
    }
 
    setTimeout(() => {
        console.log("setTimeout 宏任务") //一个宏任务,加入宏任务队列
    }, 0)
    async function fun2() {
        console.log("fun2")
        return "no promise"
        // return new Promise((resolve, reject) => {
        //     resolve("fun2 微任务")
        // })
 
        // return "await"
 
    }
    fun1() //代码开始执行
    new Promise((resolve) => {
            console.log("promise2")
            resolve("微任务2")
        })
        .then((data) => { //promise状态已经fulfilled,直接加入微任务队列
            console.log(data)
        })
 
 
    console.log("同步end") // 同步代码结束

 ⛳️ 上面代码,执行结果(这种情况是: await后面的异步函数return的不是Promise)

  1. script start
  2. promise1
  3. fun1
  4. fun2
  5. promise2
  6. 同步end
  7. 微任务1
  8. no promise
  9. await堵塞
  10. 微任务2
  11. setTimeout 宏任务

⚠️ 需要注意的点

  • 从结果上来看,await后面的异步函数return的是否是Promise,执行顺序的不同点在于await后面的代码执行是在微任务2之前还是之后执行,也就是微任务队列顺序的问题
  • 当return的是Promise时:await后面的代码会在微任务2之后执行
  • 当return的不是Promise时:await后面的代码会在微任务2之前执行

⚠️ 当return的不是promise时

  • async声明的异步函数会自动将返回包装成promise ,到调用fun2这个异步函数时,await就可以看做是使用then方法,会将回调函数(这里就是await后面的代码) 放入微任务队列,这里是排在微任务一之后,微任务二之前,所以这里await后面的代码会在微任务2之前执行

⚠️ 当return的是promise时

  • 当resolve的参数是一个promise时,此时会将这个参数promise放入微任务队列(此时排在微任务1之后),而他的父级promise状态不会改变,等到微任务队列中执行到这个promise时,父promise的then方法绑定到子promise上,所以这就解释了上面的执行顺序的问题

你可能感兴趣的:(javascript,前端)