一道setTimeout async promise执行顺序的笔试题引起的思考

====听说这是今日头条去年的一道笔试题,主要考察的是setTimeout async promise执行顺序

~先双手奉上这道题目~promise

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');
  • 首先,咱们先来了解几个概念:

    JS众所周知是单线程语言,Javascript引擎同一时刻只能执行一个代码块,使用Event Loop做为它的异步执行机制浏览器

  • 那么Event Loop是如何实现异步呢,我的浅显的理解以下:异步

    1. 同步代码按照上下文的顺序放进主进程中去执行
    2. 异步函数放进异步队列中,等待执行,在异步队列执行的顺序按照先进先出的原则
    3. 等主进程中的同步函数执行完毕后,轮询去执行异步队列中的异步函数async

      ⚠️注意: setTimeOut并非直接的把你的回掉函数放进上述的异步队列中去,而是在定时器的时间到了以后,把回掉函数放到执行异步队列中去。若是此时这个队列已经有不少任务了,那就排在他们的后面。这也就解释了为何setTimeOut为何不能精准的执行的问题了。setTimeOut执行须要知足两个条件:函数

      1. 主进程必须是空闲的状态,若是到时间了,主进程不空闲也不会执行你的回掉函数 
      2. 这个回掉函数须要等到插入异步队列时前面的异步函数都执行完了,才会执行
  • 理解了Eventloop异步实现的方式,再来补充一下promise、async/awaitoop

    1. 首先,new Promise是同步的任务,会被放到主进程中去当即执行。而.then()函数是异步任务会放到异步队列中去,那何时放到异步队列中去呢?当你的promise状态结束的时候,就会当即放进异步队列中去了。若是你要问他和setTimeOut谁当进去的快,要从下面两个方面考虑:线程

      1. promise结束时。.then内函数插入异步队列的时间与setTimeOut的回掉函数插入队列的时间,谁的早,谁的就最快
      2. **若是promise是同步的而setTimeOut时间是0,那么是promise先执行**。至于什么,查了不少的资料,了解到:一个浏览器环境只能有一个事件循环,而一个事件循环能够有多个任务队列。settimeout所在的队列与promise.then()的队列不一样,面对此种状况,v8实现的时候会先从promise.then()的队列取任务,可是并无很理解,若是有大佬愿意指点迷津,请留言告知🙏
    2. 带async关键字的函数会返回一个promise对象,若是里面没有await,执行起来等同于普通函数;若是没有await,async函数并无很厉害是否是
    3. await 关键字要在 async 关键字函数的内部,await 写在外面会报错;await如同他的语意,就是在等待,等待右侧的表达式完成。此时的await会让出线程,阻塞async内后续的代码,先去执行async外的代码。等外面的同步代码执行完毕,才会执行里面的后续代码。就算await的不是promise对象,是一个同步函数,也会等这样操做

接下来,带着上面的那些总结,步入正题code

分析上述的代码,给任务类型分类对象

async function async1() {
    console.log( 'async1 start’ ) // 同步代码2
    await async2()  // 执行async2
    console.log( 'async1 end’ )   // 须要等async1外面的代码执行完,而且async2也执行完才会执行
}
async function async2() {
    console.log( 'async2’ )  // 同步代码3
}

// ===================== 从这里开始了表演,因此在这里走起 ===============================

console.log( 'script start’ )    // 同步代码1

setTimeout( function () {
 //  setTimeout放入event-loop中的macro-tasks队列,暂不执行
console.log( 'setTimeout' )
}, 0 )

async1()  //  若是有await,第一个【await前面的代码】属于主进程执行—看最上面函数内分析

new Promise( function ( resolve ) {  // 注意这个方法里面的是同步
             // 同步代码4
    console.log( 'promise1’ ) 
    resolve();
} ).then(  
    //  .then()放入event-loop中的micro-tasks队列
     function () {
        console.log( 'promise2' )
    } 
)

console.log( 'script end’ )  // 同步代码5
  1. 最早输出的是同步代码,按照上下文执行顺序是排序

    'script start’
    'async1 start’
    'async2’ 
    'promise1’ 
    'script end’
  2. 接下来,给异步队列执行的排序

    第一个执行的函数是async1() ,里面有await,他要等待,等待就是要让async外面的代码先执行,外面的那些同步代码执行好了以后,在执行这个async里面的,await后面的代码执行。因此遇到await就让出了线程,给到后面的代码,那么下面的promise.then会先执行,后执行await后面的代码
    async1()的await后面和setTimeOut哪一个先执行,这个要看await等待的是什么。此处等待的是async2函数,这个函数里面没有await等待,带async关键字的函数返回的是一个promise对象,因此async1中等待的是一个promise对象,而且这个promise对象里面只有同步执行,会被放进时间循环的micro-tasks队列,该队列比setTimeOut的队列先执行,执行完了以后还不能执行setTimeOut,由于执行完了以后就不阻塞主进程了,主进程要接着执行;也就是await后面的同步代码,要先去执行主进程。因此setTimeOut是在最后执行的
    再次验证了上面所说的setTimeOut执行的必要条件之一是主进程空闲了
    所以,异步代码的执行顺序是

‘promise2'        // async函数外的代码先执行
'async1 end’    // -- await不阻塞了,async后面的同步代码
'setTimeout'   // promise.then的队列比setTimeout队列先执行

若是你真正的理解了Event Loop的执行机制,而且知道setTimeout的与promise.then并不是一个队列里面的,那么这道题就是很简单的送分题

上述若有不足或者不当指出,请各位大佬匡正~~💗

相关文章
相关标签/搜索