跳至主要内容

頂層`await`

· 閱讀時間約 5 分鐘
Myles Borins ([@MylesBorins](https://twitter.com/MylesBorins))

頂層await使開發者能夠在 async 函數之外使用 await 關鍵字。它像是一個大的 async 函數,會讓其他import它的模組在開始評估它們的主體之前等待。

舊的行為

async/await首次引入時,試圖在async函數之外使用await會導致SyntaxError錯誤。許多開發者使用立即調用的 async 函數表達式來訪問該功能。

await Promise.resolve(console.log('🎉'));
// → SyntaxError: await 僅在 async 函數中有效

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

新的行為

使用頂層await,上述代碼在模組中按預期運行:

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

注意: 頂層await僅在模組的頂層運行。它不支持經典腳本或非 async 函數。

使用場景

以下使用場景來源於規範提議資料庫

動態依賴路徑

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. 當前模組的執行被推遲,直到等待的 Promise 被解析。
  2. 父模組的執行被推遲,直到調用await的子模組及其所有兄弟模組導出綁定。
  3. 兄弟模組以及父模組的兄弟模組能夠以相同的同步順序繼續執行——假設模組圖中沒有循環或其他await的 Promise。
  4. 調用await的模組在等待的 Promise 解析後恢復執行。
  5. 父模組及後續的子樹在沒有其他await的 Promise 時繼續按同步順序執行。

這在 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 的支持