生成器函數是一個帶星號的函數,能夠暫停執行與恢復執行。 async/await 使用了 協程(Generator) 和 微任務(Promise) 兩種技術來實現。promise
function* genDemo() {
console.log("開始執行第 1 段");
yield "generator 1";
console.log("開始執行第 2 段");
yield "generator 2";
console.log("執行結束");
return "generator 3";
}
console.log("main 0");
let gen = genDemo(); // 此處不會打印"開始執行第 1 段"只有在執行 gen.next 時纔會執行
console.log(gen.next().value); // 開始執行第 1 段 generator 1
console.log("main 1");
console.log(gen.next().value);
console.log("main 2");
console.log(gen.next().value);
console.log("main 3");
複製代碼
從上面輸出結果能夠看出,生成器函數與主函數是交替執行的。
生成器函數中遇到 yield
關鍵字時,就會返回 yield 後的內容給外部並把執行權交給外部函數去執行。
外部函數又能夠經過 gen.next
恢復生成器函數的執行。瀏覽器
協程是一種比線程更加輕量級的存在,能夠當作是跑在線程上的任務。就像一個進程能夠有多個線程同樣,一個線程也能夠有多個協程。
可是,線程上同時只能執行一個協程。好比:當前執行的是 A 協程,要啓動 B,就須要將主線程的控制權交給 B 協程;A 暫停執行,B 恢復執行。一般,若是從 A 協程啓動 B 協程,咱們就把 A 協程稱爲 B 協程的父協程。併發
async 是一個經過異步執行並隱式返回 Promise做爲結果的函數。異步
async function foo() {
return 2;
}
foo(); // Promise {<resolved>: 2}
複製代碼
觀察下面代碼的輸出:async
async function foo() {
console.log(1);
let a = await 100;
console.log(a);
console.log(2);
}
console.log(0);
foo();
console.log(3);
複製代碼
輸出:0 1 3 100 2 執行流程圖以下:
函數
let promise_ = new Promise((resolve, reject) => {
resolve(100);
});
複製代碼
JS 引擎會將該任務提交到微任務隊列,而後暫停當前協程的執行,將主線程的控制權轉交給父協程執行,同時將 promise_ 對象返回給父協程(以下)。ui
async function foo() {
...
let a = await 100
...
}
console.log(foo())
//Promise {<pending>}__proto__: ... "
// [[PromiseStatus]]: "resolved"
// [[PromiseValue]]: undefined
複製代碼
主線程控制權交給父協程後,父協程調用 promise_.then 來監控 promise 狀態的改變。spa
接下來執行父協程的流程,打印出 3。隨後父協程將執行結束,在結束前,進入微任務的檢查點去執行微任務隊列,微任務隊列中有 resolve(100) 等待執行,執行到這裏時,會觸發 promise_.then 中的回調函數,以下:線程
promise_.then(value => {
// 回調函數觸發後,將主線程的控制權交給 foo 協程,並將 value 傳給協程
});
複製代碼
foo 協程激活後,將 value 的值給了變量 a,而後繼續執行後面語句,執行完成,將控制權歸還給父協程。code
async function foo() {
console.log("foo");
}
async function bar() {
console.log("bar start");
await foo();
console.log("bar end");
}
console.log("script start");
setTimeout(() => {
console.log("setTimeout");
}, 0);
bar();
new Promise(resolve => {
console.log("promise executor");
resolve();
}).then(() => {
console.log("promise then");
});
console.log("script end");
複製代碼
輸出以下:
scritp start
bar start foo
promise executor
script end
bar end
promise then
setTimeout
注意點: 第三步會輸出 foo,而不是 promise executor. 由於 await 是將 return 的值用 resolve 包裝提交到微任務隊列,console.log 語句不受影響,能夠直接輸出。
setTimeout 被放到延遲隊列中,而不是下一輪宏任務。
本輪宏任務執行完成後,會執行延遲隊列中的任務。
宏任務中父協程執行結束前,會去微任務隊列檢查執行微任務。