js事件循環機制(瀏覽器端Event Loop) 以及async/await的理解

事件循環機制

理解js的事件循環機制,可以很大程度的幫咱們更深層次的理解平時遇到的一些很疑惑的問題chrome

簡單版本

下面來看一段代碼,想一想它的結果和你的結果是否同樣promise

setTimeout(function() {
        console.log(1)
    }, 0)
    
    console.log(2)
    
    //  執行結果是 2 1

咱們能夠將js的任務分爲同步任務異步任務, 按照這種分類js的執行機制以下瀏覽器

  • 任務執行隊列分爲同步任務隊列異步任務隊列
  • 代碼執行時,遇到同步代碼,會被直接推入同步任務隊列並依次執行
  • 遇到異步代碼(如setTimeout、setInterval), 會被直接推入異步任務隊列
  • 同步任務隊列執行完畢,這個時候異步任務隊列的任務會被依次推入同步任務隊列並依次執行

因此上面的代碼執行的時候, setTimeout()不會被當即執行,會被推到異步任務隊列裏面, 以後再執行console.log(2), 同步任務隊列任務執行完畢以後,會去異步任務隊列的任務會被依次推到 同步任務隊列並執行異步

終極版本

下面來看一段代碼,想一想它的結果和你的結果是否同樣async

setTimeout(function() {
        console.log(1)
    }, 0)
    
    new Promise(function(resolve, reject) {
        console.log(2)
        resolve()
    }).then((res) => {
        console.log(3)
    })
    console.log(4)
    
    // 執行結果是 2 4 3 1

js異步任務按照準確的劃分,應該將任務分爲函數

  • 宏任務: setTimeoutsetInterval
  • 微任務: 例如Promise.then方法。注意new Promsie()的時候是同步,當即執行。

注意: 如今有三個隊列: 同步隊列(也稱執行棧)、宏任務隊列、微任務隊列spa

因此針對這種機制,js的事件循環機制應該是這樣的code

  • 遇到同步代碼,依次推入同步隊列並執行
  • 當遇到setTimeout、setInterval,會被推到宏任務隊列
  • 若是遇到.then,會被看成微任務,被推入微任務隊列
  • 同步隊列執行完畢,而後會去微隊列取任務,直到微隊列清空。而後檢查宏隊列,去宏隊列取任務,而且每個宏任務執行完畢都會去微隊列跑一遍,看看有沒有新的微任務,有的話再把微任務清空。這樣依次循環
console.log(1);
    
 setTimeout(() => {
   console.log('setTimeout');
 }, 0);

 let promise = new Promise(resolve => {
   console.log(3);
   resolve();
 }).then(data => {
   console.log(100);
 }).then(data => {
   console.log(200);
 });
    
 console.log(2);

因此對於以上的代碼執行流程以下:對象

  1. 遇到同步任務先輸出1。
  2. setTimeout是宏任務,會先放到宏任務隊列中。
  3. new Promise是當即執行的,因此會先輸出3。
  4. Promise.then是微任務,會依次排列到微任務隊列中,繼續向下執行輸出2。
  5. 如今執行棧中的任務已經清空,再將微任務隊列清空,依次輸出100和200。
  6. 而後每次取出一個宏任務,由於如今只有一個宏任務,因此最後輸出setTimeout

async/await

async

當咱們在函數前使用async的時候,使得該函數返回的是一個Promise對象隊列

async function test() {
    return 1   // async的函數會在這裏幫咱們隱士使用Promise.resolve(1)
}
// 等價於下面的代碼
function test() {
   return new Promise(function(resolve, reject) {
       resolve(1)
   })
}

可見async只是一個語法糖,只是幫助咱們返回一個Promise而已

await

await表示等待,是右側「表達式」的結果,這個表達式的計算結果能夠是 Promise 對象的值或者一個函數的值(換句話說,就是沒有特殊限定)。而且只能在帶有async的內部使用

使用await時,會從右往左執行,當遇到await時,會阻塞函數內部處於它後面的代碼,去執行該函數外部的同步代碼,當外部同步代碼執行完畢,再回到該函數內部執行剩餘的代碼, 而且當await執行完畢以後,會先處理微任務隊列的代碼

下面來看一個栗子:

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

下面是在chrome瀏覽器上輸出的結果
clipboard.png
使用事件循環機制分析:

  1. 首先執行同步代碼,console.log( 'script start' )
  2. 遇到setTimeout,會被推入宏任務隊列
  3. 執行async1(), 它也是同步的,只是返回值是Promise,在內部首先執行console.log( 'async1 start' )
  4. 而後執行async2(), 而後會打印console.log( 'async2' )
  5. 從右到左會執行, 當遇到await的時候,阻塞後面的代碼,去外部執行同步代碼
  6. 進入 new Promise,打印console.log( 'promise1' )
  7. .then放入事件循環的微任務隊列
  8. 繼續執行,打印console.log( 'script end' )
  9. 外部同步代碼執行完畢,接着回到async1()內部, 因爲async2()實際上是返回一個Promise, await async2()至關於獲取它的值,其實就至關於這段代碼Promise.resolve(undefined).then((undefined) => {}),因此.then會被推入微任務隊列, 因此如今微任務隊列會有兩個任務。接下來處理微任務隊列,打印console.log( 'promise2' ),後面一個.then不會有任何打印,可是會執行
  10. 執行後面的代碼, 打印console.log( 'async1 end' )
  11. 進入第二次事件循環,執行宏任務隊列, 打印console.log( 'setTimeout' )
相關文章
相關標籤/搜索