第四题:关于异步问题的笔试题

这是一道涉及事件循环的笔试题:

题目:

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');

//  script start
//  async1 start
//  async2
//  promise1
//  script end'
//  async1 end
//  promise2
//  setTimeout

在分析这道题前先说说几个知识点:

任务队列
  • JS分为同步任务和异步任务
  • 同步任务都在主线程上执行,形成一个执行栈
  • 主线程之外,事件触发线程管理的一个任务队列
  • 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行
宏任务

(macro)task(又称为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务。

浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束之后,在下一个(macro)task执行开始前,对页面进行重新渲染:

    (macro)task -> 渲染 -> (macro) task ...

(macro)task主要包括:

  • script(整体代码)
  • setTimeout
  • setInterval
  • I/O
  • UI事件
  • postMessage
  • MessageChannel
  • setImmediate(Node.js环境)
微任务

microtask(又称为微任务),可以理解是在当前task执行结束后立即执行的任务。也就是在当前task任务后,在下一个task之前,在渲染之前。

在某个宏任务执行之后,就会将在它执行期间产生的所有微任务都执行完毕。

microtask主要包括:

  • Promise.then
  • MutaionObserver
  • process.nextTick(Node.js环境)
运行机制

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

流程图如下:
在这里插入图片描述

Promise和aysnc中的立即执行

Promise中的异步体现在then和catch中,所以写在Promise中的代码是被当做同步任务立即执行的。

在async/await中,在出现await之前,其中的代码也是立即执行,在出现await时发生了什么?

await是一个让出线程的标志。await后面紧跟着的表达式会先执行,将接着await下面的代码加到micromask中,然后就会跳出整个 async函数执行后面的代码。

由于因为async await 本身就是promise+generator的语法糖。所以await后面的代码是microtask。所以对于本题中的:

    async function async1() {
        console.log('async1 start');
        await async2();
        console.log('async1 end');
    }

    等价于:

    async function async1() {
         console.log('async1 start');
         Promise.reselve(async2().then(() => {
             console.log('async1 end');
         }))
    }
回到本题

了解上面的知识点后,对于本题就很简单了:

  • 首先进入第一个宏任务(script整体代码),遇到同步任务直接执行,即输出 ‘script start’

  • 遇到setTimeout将其放到宏任务队列

  • 遇到async1(),将async1函数里面的同步任务直接执行,输出‘async1 start’,然后将await紧跟着的表达式执行,执行async2(),输出‘async2’,最后将await后面的代码加入到微任务队列,跳出函数async1,执行下面的代码

  • 遇到Promise,将里面的同步任务立即执行,输出‘promise1’,将then后面的代码加入到微任务队列.

  • 最后立即执行console.log(‘script end’),输出‘script end’。

  • 到此,第一个宏任务结束,紧接着执行这一轮的微任务,依次执行微任务队列,输出‘async1 end’,再输出‘promise2’

  • 微任务执行完毕,接着执行下一个宏任务,这里只有setTimeout,执行它,输出‘setTimeout’

  • 结束流程

    变式一
      async function async1() {
          console.log('async1 start');
          await async2();
          console.log('async1 end');
      }
      async function async2() {
          //async2做出如下更改:
          new Promise(function(resolve) {
          console.log('promise1');
          resolve();
      }).then(function() {
          console.log('promise2');
      });
      }
      console.log('script start');
    
      setTimeout(function() {
          console.log('setTimeout');
      }, 0)
    
      async1();
    
      new Promise(function(resolve) {
          console.log('promise3');
          resolve();
      }).then(function() {
          console.log('promise4');
      });
    
      console.log('script end');
    
      // script start
      // async1 start
      // promise1
      // promise3
      // script end
      // promise2
      // async1 end
      // promise4
      // setTimeout
    
变式二
    async function async1() {
        console.log('async1 start');
        await async2();
        //更改如下:
        setTimeout(function() {
        console.log(' setTimeout1') // 第三个宏任务
    },0)
    }

    async function async2() {
        //更改如下:
        setTimeout(function() {
	    console.log('setTimeout2')
    },0)                // 第二个宏任务
    }

    console.log('script start');

    setTimeout(function() {
        console.log('setTimeout3');
    }, 0)   // 第一个宏任务

    async1();

    new Promise(function(resolve) {
        console.log('promise1');
        resolve();
    }).then(function() {
        console.log('promise2');
    });

    console.log('script end');

    //  script start
    //  async1 start
    //  promise1
    //  script end
    //  promise2
    // setTimeout3
    // setTimeout2
    // setTimeout1
变式三
    async function a1 () {
        console.log('a1 start');
        await a2();
        console.log('a1 end');// 第二个微任务
    }
    async function a2 () {
        console.log('a2');
    }

    console.log('script start');

    setTimeout(() => {
        console.log('setTimeout')
    }, 0) // 第一个宏任务

    Promise.resolve().then(() => {
        console.log('promise1');
    }) // 第一个微任务

    a1();

    let promise2 = new Promise((resolve) => {
        resolve('promise2.then')
        console.log('promise2')
    })

    promise2.then((res) => {
        console.log(res)// 第三个微任务
        Promise.resolve().then(() => {
            console.log('promise3')//第4个微任务
        })
    })

    console.log('script end')
    // script start
    // a1 start
    // a2
    // promise2
    // script end
    // promise1
    // a1 end
    // promise2.then
    // promise3
    // setTimeout
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值