深入掌握 ECMAScript 6 非同步程式設計(四):async函式的含義與用法

發表於2015-10-14

本文是《深入掌握 ECMAScript 6 非同步程式設計》系列文章的最後一篇。

一、終極解決

非同步操作是 JavaScript 程式設計的麻煩事,麻煩到一直有人提出各種各樣的方案,試圖解決這個問題。

從最早的回撥函式,到 Promise 物件,再到 Generator 函式,每次都有所改進,但又讓人覺得不徹底。它們都有額外的複雜性,都需要理解抽象的底層執行機制。

非同步I/O不就是讀取一個檔案嗎,幹嘛要搞得這麼複雜?非同步程式設計的最高境界,就是根本不用關心它是不是非同步。

async 函式就是隧道盡頭的亮光,很多人認為它是非同步操作的終極解決方案。

二、async 函式是什麼?

一句話,async 函式就是 Generator 函式的語法糖。

前文有一個 Generator 函式,依次讀取兩個檔案。

 
寫成 async 函式,就是下面這樣。

 
一比較就會發現,async 函式就是將 Generator 函式的星號(*)替換成 async,將 yield 替換成 await,僅此而已。

三、async 函式的優點

async 函式對 Generator 函式的改進,體現在以下三點。

(1)內建執行器。 Generator 函式的執行必須靠執行器,所以才有了 co 函式庫,而 async 函式自帶執行器。也就是說,async 函式的執行,與普通函式一模一樣,只要一行。

 
(2)更好的語義。 async 和 await,比起星號和 yield,語義更清楚了。async 表示函式裡有非同步操作,await 表示緊跟在後面的表示式需要等待結果。

(3)更廣的適用性。 co 函式庫約定,yield 命令後面只能是 Thunk 函式或 Promise 物件,而 async 函式的 await 命令後面,可以跟 Promise 物件和原始型別的值(數值、字串和布林值,但這時等同於同步操作)。

四、async 函式的實現

async 函式的實現,就是將 Generator 函式和自動執行器,包裝在一個函式裡。

 
所有的 async 函式都可以寫成上面的第二種形式,其中的 spawn 函式就是自動執行器。

下面給出 spawn 函式的實現,基本就是前文自動執行器的翻版。

 
async 函式是非常新的語法功能,新到都不屬於 ES6,而是屬於 ES7。目前,它仍處於提案階段,但是轉碼器 Babel 和 regenerator 都已經支援,轉碼後就能使用。

五、async 函式的用法

同 Generator 函式一樣,async 函式返回一個 Promise 物件,可以使用 then 方法新增回撥函式。當函式執行的時候,一旦遇到 await 就會先返回,等到觸發的非同步操作完成,再接著執行函式體內後面的語句。

下面是一個例子。

 
上面程式碼是一個獲取股票報價的函式,函式前面的async關鍵字,表明該函式內部有非同步操作。呼叫該函式時,會立即返回一個Promise物件。

下面的例子,指定多少毫秒後輸出一個值。

 
上面程式碼指定50毫秒以後,輸出”hello world”。

六、注意點

await 命令後面的 Promise 物件,執行結果可能是 rejected,所以最好把 await 命令放在 try…catch 程式碼塊中。

 
await 命令只能用在 async 函式之中,如果用在普通函式,就會報錯。

 
上面程式碼會報錯,因為 await 用在普通函式之中了。但是,如果將 forEach 方法的引數改成 async 函式,也有問題。

 
上面程式碼可能不會正常工作,原因是這時三個 db.post 操作將是併發執行,也就是同時執行,而不是繼發執行。正確的寫法是採用 for 迴圈。

 
如果確實希望多個請求併發執行,可以使用 Promise.all 方法。

 

 

相關文章