頂層 await

發布於 · 標記為 ECMAScript

頂層 await 讓開發人員可以在非非同步函式之外使用 await 關鍵字。它就像一個大型非同步函式,導致 import 它們的其他模組在開始評估其主體之前等待。

舊行為 #

async/await 首次推出時,嘗試在 async 函式之外使用 await 會導致 SyntaxError。許多開發人員利用立即呼叫的非同步函式表達式來取得該功能。

await Promise.resolve(console.log('🎉'));
// → SyntaxError: await is only valid in async function

(async function() {
await Promise.resolve(console.log('🎉'));
// → 🎉
}());

新行為 #

使用頂層 await 時,上述程式碼會在 模組 中以您預期的方式運作

await Promise.resolve(console.log('🎉'));
// → 🎉

注意:頂層 await 在模組的頂層運作。不支援傳統指令碼或非非同步函式。

使用案例 #

這些使用案例取自 規格提案存放庫

動態相依路徑 #

const strings = await import(`/i18n/${navigator.language}`);

這允許模組使用執行時期值來確定相依性。這對於開發/生產分割、國際化、環境分割等事項很有用。

資源初始化 #

const connection = await dbConnector();

這允許模組表示資源,並在無法使用模組時產生錯誤。

相依備援 #

以下範例嘗試從 CDN A 載入 JavaScript 函式庫,如果失敗則備援到 CDN B

let jQuery;
try {
jQuery = await import('https://cdn-a.example.com/jQuery');
} catch {
jQuery = await import('https://cdn-b.example.com/jQuery');
}

模組執行順序 #

頂層 await 對 JavaScript 最大的變更之一是圖表中模組的執行順序。JavaScript 引擎以 後序遍歷 執行模組:從模組圖表的左子樹開始,評估模組,匯出其繫結,然後執行其同層節點,接著是其父節點。此演算法會遞迴執行,直到執行模組圖表的根節點。

在頂層 await 之前,此順序總是同步且確定性的:在多次執行您的程式碼之間,您的圖表保證會以相同的順序執行。一旦頂層 await 上線,就會有相同的保證,但僅在您不使用頂層 await 的情況下。

當您在模組中使用頂層 await 時,會發生下列情況

  1. 目前模組的執行會延遲,直到等待中的承諾已解決。
  2. 父模組的執行會延遲,直到呼叫 await 的子模組及其所有同層模組匯出繫結。
  3. 同層模組和父模組的同層模組能夠以相同的同步順序繼續執行,前提是圖形中沒有循環或其他 await 的承諾。
  4. 呼叫 await 的模組會在 await 的承諾解決後繼續執行。
  5. 父模組和後續樹狀結構會繼續以同步順序執行,只要沒有其他 await 的承諾。

這不是已經在 DevTools 中運作了嗎?#

確實如此!Chrome DevToolsNode.js 和 Safari Web Inspector 中的 REPL 已經支援頂層 await 一段時間了。然而,此功能是非標準的,且僅限於 REPL!它不同於頂層 await 提案,後者是語言規格的一部分,且僅適用於模組。若要以完全符合規格提案語意的方式測試依賴於頂層 await 的生產程式碼,請務必在您的實際應用程式中進行測試,而不要只在 DevTools 或 Node.js REPL 中進行測試!

頂層 await 不會造成問題嗎?#

您可能看過 臭名昭著的 gist,由 Rich Harris 所撰寫,最初概述了關於頂層 await 的一些疑慮,並敦促 JavaScript 語言不要實作此功能。一些具體疑慮包括

  • 頂層 await 可能會阻擋執行。
  • 頂層 await 可能會阻擋擷取資源。
  • 對於 CommonJS 模組,不會有明確的互通性。

提案的第 3 階段版本直接解決了這些問題

  • 由於同層模組能夠執行,因此沒有明確的阻擋。
  • 模組圖表的執行階段中會發生頂層 await。此時,所有資源都已擷取並連結。不會有封鎖擷取資源的風險。
  • 頂層 await 僅限於模組。明確不支援腳本或 CommonJS 模組。

與任何新的語言功能一樣,總是有發生意外行為的風險。例如,使用頂層 await 時,循環模組相依性可能會造成死結。

在沒有頂層 await 的情況下,JavaScript 開發人員經常使用非同步立即呼叫函式表達式,只是為了存取 await。很遺憾地,這種模式會導致圖表執行較不確定,以及應用程式的靜態分析能力較差。基於這些原因,缺少頂層 await 被視為比功能中引入的危害更高的風險。

支援頂層 await #