什麼是Generator
函數
Generator
函數與普通函數的區別是該函數可以分步驟阻塞,不像普通函數需要一路走到底,就像是Generator
生成一堆的小函數,只有主動調用next()
纔會一個個的執行這些小函數。總結起來就是Generator
函數中間可以停下來,可以使用yield
來暫時的放棄執行。
拿一個形象的例子:普通函數好比是坐高鐵或者乘坐飛機,我們只有到達目的地了,才停止下來,中間是不允許有停歇的,但是
Generator
函數好比是乘坐計程車,當我們需要在某個地方去做什麼事,比如上個廁所,是可以叫師傅在某地方等一會,回來了再繼續趕往目的地
yield
的理解與使用
Generator
是一個狀態機,封裝了多個內部狀態,執行Generator
函數會返回一個遍歷器對象,返回的遍歷器對象,可以使用next
依次遍歷Generator
函數內部的每一個狀態。Generator
函數是分段執行的,yield
表達式是暫停執行的標記,而next
方法可以恢復執行。Generator
函數的執行必須靠執行器。
yield
可以傳參也可以返回內容(一般的返回的是上一步執行的結果)。一個yield就是一堵牆,牆的兩面都是小的函數,只有牆的一面的函數執行完了,纔可以接着執行另一面的函數。如下圖幫助理解:
yield
傳參的形式與意義,如下圖:
Generator
與yield
綜合使用,如下僞代碼:
Generator
相比Promise
的優勢
Promise
也是處理異步的,但爲什麼會再出現generator
,是因爲當我們使用promise
的時候,如果中間需要有一些邏輯的判斷,則這樣寫出來的promise
代碼相比普通的函數而言是沒有任何的優勢的。但是如果使用generator
可以簡化很多,並且是有序,正確的執行 每道工序。
總結:
promise
適合處理一次讀一堆的異步操作,而generator
適合在讀的過程中有一些邏輯的處理,分批,有順序的處理。簡單的來說是generator
是對promise
的封裝
async
函數
async
函數是Generator
函數的語法糖,將Generator
的星號換成async
將yield
換成await
,async
函數比Generator
函數更好用
- 自帶執行器,執行起來,跟調用普通函數一樣
async
和await
語義更清晰,async
表示函數裏有異步操作,await
表示緊跟在後面的表達式需要等待結果await
後面啥都可以跟,可以是Promise
也可以是對象和原始類型的值(數值、字符串和布爾值,但這時等同於同步操作async
函數的 返回值是Promise
正常情況下,await
命令是個Promise
對象,如果不是 會被轉成一個 立即resolved
的對象,async
函數完全可以看作多個異步操作,包裝成的一個Promise
對象(因爲await
函數返回的是Promise
對象),而await
命令就是內部then
命令的語法糖。
然而,然而,我們沒寫錯誤處理。
async function f() {
return 'hello world';
}
f().then().catch()
正常情況下 async 函數中return結果會使Promise對象變爲 resolved狀態,返回值作爲then方法回調函數的參數,而出錯則會使Promise對象的變爲reject狀態,錯誤會被catch捕獲。
因爲 async函數 相當於對 多個Promise的封裝,所以必須等到內部所有的await命令執行完,纔會改變自己的狀態爲resolved,除非 碰到return語句或者拋出了異常。
也就是說,正常情況下 只有async函數內部的異步操作執行完,纔會執行then後面的語句。
只要一個await後面的Promise變爲rejected,整個async函數就會中斷執行,整個async返回的Promise對象就會是rejected狀態
async function f() {
await Promise.reject('出錯了');
await Promise.resolve('hello world'); // 不會執行
}
因爲第一個await後面的對象reject了,所以整個async函數就中斷執行了
有時,我們希望即使前一個異步操作失敗,也不要中斷後面的異步操作。
這時可以將第一個await放在try…catch結構裏面,這樣不管這個異步操作是否成功,第二個await都會執行。
await命令後面的Promise對象,運行結果可能是rejected,所以最好把await命令放在try…catch代碼塊中。
try catch
try catch是JavaScript的異常處理機制,把可能出錯的代碼放在try語句塊中,如果出錯了,就會被catch捕獲來處理異常。如果不catch 一旦出錯就會造成程序崩潰。
如果有多個await命令,可以將其都放在try catch結構中,如果執行出錯,catch會去捕獲異常
async function f() {
try {
await Promise.reject('出錯了');
console.log('上面已經出錯了');
return await Promise.resolve('hello world');
} catch(e) {
console.log(e);
}
}
f()
.then(v => console.log(v))
catch會去捕獲try代碼塊中的錯誤,只要有一個拋出了異常,就不會繼續執行,所以上面的代碼不會打印上面已經出錯了也不會執行return await Promise.resolve(‘hello world’);
因爲使用了trycatch 所以 async 是順利執行完成的,其中的報錯 被 try catch處理了,所以異常不會被async返回的Promise的catch捕獲,因此async返回的Promise對象狀態是resolved。
如果異步函數沒有依賴關係,最好併發執行
await 會等待後面的異步操作執行完畢,纔會繼續執行
let foo = await getFoo();
let bar = await getBar();
上面的代碼會順序執行,
如果需要多個await沒有相互依賴,最好讓他們同時觸發,可以使用以下兩種方式:
使用Promise.all() 包裝一個新的Promise對象
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
不等待分別執行,返回新的Promise對象
//沒用await 立即執行返回 Promise對象
let fooPromise = getFoo();
let barPromise = getBar();
// 等待 Promise對象的結果 之前也說過 await就像是then的語法糖
let foo = await fooPromise;
let bar = await barPromise;