一道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並不是一個隊列裏面的,那麼這道題就是很簡單的送分題

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

相關文章
相關標籤/搜索