轉載:https://segmentfault.com/a/1190000017554062
事件循環機制
理解js的事件循環機制,能夠很大程度的幫我們更深層次的理解平時遇到的一些很疑惑的問題
簡單版本
下面來看一段代碼,想想它的結果和你的結果是否一樣
setTimeout(function() { console.log(1) }, 0) console.log(2) // 執行結果是 2 1
我們可以將js的任務分為同步任務和異步任務, 按照這種分類js的執行機制如下
- 任務執行隊列分為同步任務隊列和異步任務隊列
- 代碼執行時,遇到同步代碼,會被直接推入同步任務隊列並依次執行
- 遇到異步代碼(如
setTimeout、setInterval), 會被直接推入異步任務隊列 - 當同步任務隊列執行完畢,這個時候異步任務隊列的任務會被依次推入同步任務隊列並依次執行
所以上面的代碼執行的時候, setTimeout()不會被立即執行,會被推到異步任務隊列里面, 之后再執行console.log(2), 同步任務隊列任務執行完畢之后,會去異步任務隊列的任務會被依次推到 同步任務隊列並執行
終極版本
下面來看一段代碼,想想它的結果和你的結果是否一樣
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異步任務按照准確的划分,應該將任務分為
- 宏任務:
setTimeout、setInterval - 微任務: 例如
Promise.then方法。注意new Promsie()的時候是同步,立即執行。
注意: 現在有三個隊列: 同步隊列(也稱執行棧)、宏任務隊列、微任務隊列
所以針對這種機制,js的事件循環機制應該是這樣的
- 遇到同步代碼,依次推入同步隊列並執行
- 當遇到
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。
setTimeout是宏任務,會先放到宏任務隊列中。new Promise是立即執行的,所以會先輸出3。- 而
Promise.then是微任務,會依次排列到微任務隊列中,繼續向下執行輸出2。 - 現在執行棧中的任務已經清空,再將微任務隊列清空,依次輸出100和200。
- 然后每次取出一個宏任務,因為現在只有一個宏任務,所以最后輸出
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瀏覽器上輸出的結果
使用事件循環機制分析:
- 首先執行同步代碼,
console.log( 'script start' ) - 遇到
setTimeout,會被推入宏任務隊列 - 執行
async1(), 它也是同步的,只是返回值是Promise,在內部首先執行console.log( 'async1 start' ) - 然后執行
async2(), 然后會打印console.log( 'async2' ) - 從右到左會執行, 當遇到
await的時候,阻塞后面的代碼,去外部執行同步代碼 - 進入
new Promise,打印console.log( 'promise1' ) - 將
.then放入事件循環的微任務隊列 - 繼續執行,打印
console.log( 'script end' ) - 外部同步代碼執行完畢,接着回到
async1()內部, 由於async2()其實是返回一個Promise,await async2()相當於獲取它的值,其實就相當於這段代碼Promise.resolve(undefined).then((undefined) => {}),所以.then會被推入微任務隊列, 所以現在微任務隊列會有兩個任務。接下來處理微任務隊列,打印console.log( 'promise2' ),后面一個.then不會有任何打印,但是會執行 - 執行后面的代碼, 打印
console.log( 'async1 end' ) - 進入第二次事件循環,執行宏任務隊列, 打印
console.log( 'setTimeout' )
