解決函數(shù)回調經(jīng)歷了幾個階段救崔, Promise 對象锭部, Generator 函數(shù)到async函數(shù)。async函數(shù)目前是解決函數(shù)回調的最佳方案杖爽。很多語言目前都實現(xiàn)了async敲董,包括Python 紫皇,java spring慰安,go等。
async await 的用法
async 函數(shù)返回一個 Promise 對象聪铺,當函數(shù)執(zhí)行的時候化焕,一旦遇到 await 就會先返回,等到觸發(fā)的異步操作完成铃剔,再接著執(zhí)行函數(shù)體內(nèi)后面的語句撒桨。
function getNum(num){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num+1)
}, 1000)
})
}
const func = async ()=>{
const f1 = await getNum(1)
const f2 = await getNum(f1)
console.log(f2)
// 輸出3
}
func()
async /await 需要在function外部書寫async,在內(nèi)部需要等待執(zhí)行的函數(shù)前書寫await即可
深入理解
理解async函數(shù)需要先理解Generator函數(shù)键兜,因為async函數(shù)是Generator函數(shù)的語法糖凤类。
Generator[?d??n??ret?]函數(shù)-生成器
Generator是ES6標準引入的新的數(shù)據(jù)類型。Generator可以理解為一個狀態(tài)機普气,內(nèi)部封裝了很多狀態(tài)谜疤,同時返回一個迭代器Iterator對象∠志鳎可以通過這個迭代器遍歷相關的值及狀態(tài)夷磕。
Generator的顯著特點是可以多次返回,每次的返回值作為迭代器的一部分保存下來仔沿,可以被我們顯式調用坐桩。
Generator函數(shù)的聲明
一般的函數(shù)使用function聲明,return作為回調(沒有遇到return封锉,在結尾調用return undefined)绵跷,只可以回調一次膘螟。而Generator函數(shù)使用function*定義,除了return碾局,還使用yield返回多次萍鲸。
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.next(); // {value: 2, done: false}
result.next(); // {value: 3, done: true}
result.next(); //{value: undefined, done: true}
在chrome瀏覽器中這個例子里,我們可以看到擦俐,在執(zhí)行foo函數(shù)后返回了一個
Generator函數(shù)的實例脊阴。它具有狀態(tài)值suspended和closed,suspended代表暫停蚯瞧,closed則為結束嘿期。但是這個狀態(tài)是無法捕獲的,我們只能通過Generator函數(shù)的提供的方法獲取當前的狀態(tài)埋合。
在執(zhí)行next方法后备徐,順序執(zhí)行了yield的返回值。返回值有value和done兩個狀態(tài)甚颂。value為返回值蜜猾,可以是任意類型。done的狀態(tài)為false和true振诬,true即為執(zhí)行完畢蹭睡。在執(zhí)行完畢后再次調用返回{value: undefined, done: true}
注意:在遇到return的時候,所有剩下的yield不再執(zhí)行赶么,直接返回{ value: undefined, done: true }
Generator函數(shù)的方法
Generator函數(shù)提供了3個方法肩豁,next/return/throw
next方式是按步執(zhí)行,每次返回一個值,同時也可以每次傳入新的值作為計算
function* foo(x) {
let a = yield x + 1;
let b= yield a + 2;
return x + 3;
}
const result = foo(0) // foo {<suspended>}
result.next(1); // {value: 1, done: false}
result.next(2); // {value: 2, done: false}
result.next(3); // {value: 3, done: true}
result.next(4); //{value: undefined, done: true}
return則直接跳過所有步驟辫呻,直接返回 {value: undefined, done: true}
throw則根據(jù)函數(shù)中書寫try catch返回catch中的內(nèi)容清钥,如果沒有寫try,則直接拋出異常
function* foo(x) {
try{
yield x+1
yield x+2
yield x+3
yield x+4
}catch(e){
console.log('catch it')
}
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.next(); // {value: 2, done: false}
result.throw(); // catch it {value: undefined, done: true}
result.next(); //{value: undefined, done: true}
這里可以看到在執(zhí)行throw之前放闺,順序的執(zhí)行了狀態(tài)祟昭,但是在遇到throw的時候,則直接走進catche并改變了狀態(tài)怖侦。
這里還要注意一下篡悟,因為狀態(tài)機是根據(jù)執(zhí)行狀態(tài)的步驟而執(zhí)行,所以如果執(zhí)行thow的時候础钠,沒有遇到try catch則會直接拋錯
以下面兩個為例
function* foo(x) {
yield x+1
try{
yield x+2
}catch(e){
console.log('catch it')
}
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.next(); // {value: 2, done: false}
result.throw(); // catch it {value: undefined, done: true}
result.next(); //{value: undefined, done: true}
這個例子與之前的執(zhí)行狀態(tài)一樣恰力,因為在執(zhí)行到throw的時候,已經(jīng)執(zhí)行到try語句旗吁,所以可以執(zhí)行踩萎,而下面的例子則不一樣
function* foo(x) {
yield x+1
try{
yield x+2
}catch(e){
console.log('catch it')
}
}
const result = foo(0) // foo {<suspended>}
result.next(); // {value: 1, done: false}
result.throw(); // Uncaught undefined
result.next(); //{value: undefined, done: true}
執(zhí)行throw的時候,還沒有進入到try語句很钓,所以直接拋錯香府,拋出undefined為throw未傳參數(shù)董栽,如果傳入?yún)?shù)則顯示為傳入的參數(shù)。此狀態(tài)與未寫try的拋錯狀態(tài)一致企孩。
遍歷
Generator函數(shù)的返回值是一個帶有狀態(tài)的Generator實例锭碳。它可以被for of 調用,進行遍歷勿璃,且只可被for of 調用擒抛。此時將返回他的所有狀態(tài)
function* foo(x) {
console.log('start')
yield x+1
console.log('state 1')
yield x+2
console.log('end')
}
const result = foo(0) // foo {<suspended>}
for(let i of result){
console.log(i)
}
//start
//1
//state 1
//2
//end
result.next() //{value: undefined, done: true}
調用for of方法后,在后臺調用next(),當done屬性為true的時候补疑,循環(huán)退出歧沪。因此Generator函數(shù)的實例將順序執(zhí)行一遍,再次調用時莲组,狀態(tài)為已完成
狀態(tài)的存儲和改變
Generator函數(shù)中yield返回的值是可以被變量存儲和改變的诊胞。
function* foo(x) {
let a = yield x + 0;
let b= yield a + 2;
yield x;
yield a
yield b
}
const result = foo(0)
result.next() // {value: 0, done: false}
result.next(2) // {value: 4, done: false}
result.next(3) // {value: 0, done: false}
result.next(4) // {value: 2, done: false}
result.next(5) // {value: 3, done: false}
以上的執(zhí)行結果中,我們可以看到锹杈,在第二步的時候撵孤,我們傳入2這個參數(shù),foo函數(shù)中的a的變量的值0被替換為2竭望,并且在第4次迭代的時候邪码,返回的是2。而第三次迭代的時候市框,傳入的3參數(shù)霞扬,替換了b的值4糕韧,并在第5次迭代的時候返回了3枫振。所以傳入的參數(shù),是替代上一次迭代的生成值萤彩。
yield 委托*
在Generator函數(shù)中粪滤,我們有時需要將多個迭代器的值合在一起,我們可以使用yield *的形式雀扶,將執(zhí)行委托給另外一個Generator函數(shù)
function* foo1() {
yield 1;
yield 2;
return "foo1 end";
}
function* foo2() {
yield 3;
yield 4;
}
function* foo() {
yield* foo1();
yield* foo2();
yield 5;
}
const result = foo();
console.log(iterator.next());// "{ value: 1, done: false }"
console.log(iterator.next());// "{ value: 2, done: false }"
console.log(iterator.next());// "{ value: 3, done: false }"
console.log(iterator.next());// "{ value: 4, done: false }"
console.log(iterator.next());// "{ value: 5, done: false }"
console.log(iterator.next());// "{ value: undefined, done: true }"
foo在執(zhí)行的時候杖小,首先委托給了foo1,等foo1執(zhí)行完畢愚墓,再委托給foo2予权。但是我們發(fā)現(xiàn),”foo1 end” 這一句并沒有輸出浪册。
在整個Generator中扫腺,return只能有一次,在委托的時候村象,所有的yield*都是以函數(shù)表達式的形式出現(xiàn)笆环。return的值是表達式的結果攒至,在委托結束之前其內(nèi)部都是暫停的,等待到表達式的結果的時候躁劣,將結果直接返回給foo迫吐。此時foo內(nèi)部沒有接收的變量,所以未打印账忘。
如果我們希望捕獲這個值志膀,可以使用yield *foo()的方式進行獲取。
實現(xiàn)一個簡單的async/await
如上鳖擒,我們掌握了Generator函數(shù)的使用方法梧却。async/await語法糖就是使用Generator函數(shù)+自動執(zhí)行器來運作的。 我們可以參考以下例子
// 定義了一個promise败去,用來模擬異步請求放航,作用是傳入?yún)?shù)++
function getNum(num){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num+1)
}, 1000)
})
}
//自動執(zhí)行器,如果一個Generator函數(shù)沒有執(zhí)行完圆裕,則遞歸調用
function asyncFun(func){
var gen = func();
function next(data){
var result = gen.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
// 所需要執(zhí)行的Generator函數(shù)广鳍,內(nèi)部的數(shù)據(jù)在執(zhí)行完成一步的promise之后,再調用下一步
var func = function* (){
var f1 = yield getNum(1);
var f2 = yield getNum(f1);
console.log(f2) ;
};
asyncFun(func);
在執(zhí)行的過程中吓妆,判斷一個函數(shù)的promise是否完成赊时,如果已經(jīng)完成,將結果傳入下一個函數(shù)行拢,繼續(xù)重復此步驟祖秒。
總結
async/await非常好理解,基本理解了Generator函數(shù)之后舟奠,幾句話就可以描述清楚竭缝。這里沒有過多的繼續(xù)闡述Generator函數(shù)的內(nèi)部執(zhí)行邏輯及原理,如果有對此有深入理解的童鞋沼瘫,歡迎補充說明抬纸。