async
-
async其實就是對Generator的封裝,只不過async可以自動執行next()。
-
async必須等到里面所有的await執行完,async才開始return,返回的Promise狀態才改變。除非遇到return和錯誤。
-
async默認返回一個Promise,如果return不是一個Promise對象,就會被轉為立即resolve的Promise,可以在then函數中獲取返回值。
例一
async function async1() {
console.log("async1_start_2");
await async2();
console.log("async1_end_6");
return 'async_return_8';
}
async function async2() {
console.log("async2_3");
}
console.log("script_start_1");
setTimeout(function() {
console.log("setTimeout_9");
}, 0);
async1().then(function (message) { console.log(message) });
new Promise(function(resolve) {
console.log("promise_4");
resolve();
}).then(function() {
console.log("promise_7");
});
console.log("script_end_5");
輸出為:
這道題目考查的是我們對 事件循環 任務隊列 的理解:
事件循環(Event Loop):
- JS會首先判斷代碼是同步還是異步,同步進入主線程,異步進入任務隊列;
- 同步任務進入主線程后一直執行,直到主線程空閑后,才會去任務隊列中查看是否有可執行的異步任務,如果有就推入主線程中執行;
- 事件循環是一個先進先出(FIFO)隊列,這說明回調是按照它們被加入隊列的順序執行的。
[ 分析 ]:
-
在單線程的js中,異步代碼會被放入一個事件隊列,等到所有其他代碼執行后再執行,而不會阻塞線程。我們從上到下看,首先打印:
1
; -
setTimeout / setInterval 要放到任務隊列的末尾,等待后續執行。繼續往下走;
此時的任務隊列:
- 宏任務隊列: setTimeout
- 微任務隊列:none
-
async1 開始執行,當函數里遇到await時,暫停執行(await所在行放在本次執行完),而 async1 函數 未完成部分被添加到宏任務隊列;
此時的任務隊列:
- 宏任務隊列:async1, setTimeout
- 微任務隊列:none
-
new Promise() 實例對象被new出來后,它里面的promise1會立刻打印,然后又遇到 then, 此時 promise 實例 被添加到微任務隊列;
此時的任務隊列:
- 宏任務隊列:async1 ,setTimeout
- 微任務隊列: promise實例
-
接着打印:script end。至此,同步代碼(第一個宏任務)已執行完畢。而我們的任務隊列中還存在着 async1, promise對象, setTimeout異步回調;
-
由於異步代碼第一次執行時,async1 函數 要早於 promise對象,所以緊接着 async1 函數繼續執行沒有執行完成的部分(例三中promise.then先於await,所以then先執行),執行完畢后,退出任務隊列,打印:async1 end。然后把它的 then 邏輯添加到任務微任務隊列中;
此時的任務隊列:
-
宏任務隊列:setTimeout
-
微任務隊列:promise實例 ,async1的then邏輯部分
- 先清空微任務隊列,promise 實例 繼續執行它的 then 的邏輯,打印:promise2。執行完畢后,退出微任務隊列;
此時的任務隊列:
-
宏任務隊列:setTimeout
-
微任務隊列:async1的then邏輯部分
- async 函數執行 then 邏輯;
此時的任務隊列:
-
宏任務隊列:setTimeout
-
微任務隊列:none
- setTimeout是宏任務會在最后執行。
例二
console.log(1);
async function asyncfn1(){
console.log(2);
await asyncfn2();
console.log(5);
};
setTimeout(() => {
console.log('setTimeout')
}, 0)
async function asyncfn2(){
console.log(3)
};
asyncfn1();
console.log(4);
輸出:
1
2
3
4
5
setTimeout
細品:
- 首先執行全局同步代碼,先輸出
1
; - 隨后執行
asyncfn1
,輸出2
遇到awati
后,先執行asyncfn2
,將后面的代碼放入宏任務隊列,
此時的任務隊列:
- 宏任務隊列:
asyncfn1
剩余代碼; - 微任務隊列:none;
- 執行
asyncfn2
輸出3
; - 繼續執行全局同步代碼,輸出
4
; - 執行宏任務輸出
5
;
例三
var p = new Promise((res,rej) => {
res('hello_6')
console.log('1')
})
function hello() {
console.log('hello_begins_2')
return p
}
hello().then(res => {
console.log(res)
console.log('hello_7')
return 'hello_10'
}).then(res => {
console.log(res)
console.log('hello_11')
return 'hello_13'
}).then(res => {
console.log(res)
console.log('hello_14')
})
function test1 () {
console.log('test1_5')
}
async function asy () {
console.log('asy_begins_3')
await console.log('asy_4')
console.log('async_8')
await console.log('asy_9')
console.log('asy_ends_12')
}
asy()
test1()
結果:
看官們可以根據輸出結果細品;
注意:await
后面的代碼雖然算作宏任務,但是和普通的微任務不在一個維度,位於更上一層的任務隊列,所以優先度要比其他(下層)微任務要高;
參考思路:
- 執行同步的全局代碼輸出
1
(遇到new Promise()的需要立即執行) - 第
11
行執行hello
函數,輸出2
,並返回一個Promise
對象p
,將hello函數的第一層then函數放入微任務隊列;
此時的任務隊列:
-
宏任務隊列:none
-
微任務隊列:hello.then(0)
- 繼續執行同步代碼,到第
38
行,執行asy
函數; - 在第
29
行輸出3
,隨后遇到await
,執行該行,輸出4
,剩下的代碼被放入了宏任務隊列(為了區分任務的層次,標明了序號,先執行完同層的任務,再到其他層)
此時的任務隊列:
-
宏任務隊列:asy await后代碼(0)
-
微任務隊列:hello.then(0),hello.then.then(1),hello.then.then.then(2)
- 繼續執行同步全局代碼,第40行,執行test函數,輸出
5
; - 執行微任務隊列中的hello.then,輸出返回的promise對象p中的處理結果
6
(第12->8->2行),隨后第13行輸出7
;
此時的任務隊列:
-
宏任務隊列:asy await后代碼(0)
-
微任務隊列:hello.then.then(1),hello.then.then.then(2)
- 執行第0層的宏任務,也就是asy await后的代碼,第
32
行輸出8
,第39
行遇到await,執行完該行輸出9
后,將后面的代碼推進宏任務隊列;
此時的任務隊列:
-
宏任務隊列:asy await后代碼(1)
-
微任務隊列:hello.then.then(1),hello.then.then.then(2)
- 第
15
行,執行hello函數的第二個then函數,返回處理結果res,輸出10
和11
;
此時的任務隊列:
-
宏任務隊列:asy await后代碼(1)
-
微任務隊列:hello.then.then.then(2)
- 同層的還有一個宏任務,執行asy await后的代碼,第
35
行,輸出12
;
此時的任務隊列:
-
宏任務隊列:none
-
微任務隊列:hello.then.then.then(2)
- 取出微任務隊列中的最后一個任務,回到第
19
行輸出hello的第二個then函數的處理結果,分別是13
和14
;
至此程序執行完成;