跳至内容

顶级 Await

顶级 await (TLA) 允许您在模块或文件的顶级使用 await,而不是仅在异步函数中使用。一种看待它的方式是,好像每个文件都在一个 async 函数内运行。

以下是在服务器上使用顶级 await 的示例。当加载此文件时,await 将导致模块在运行模块其余部分中的代码之前等待计数。

js
const Links = new Mongo.Collection('links');

// Async code using top level await.
// The module waits for this to finish before continuing
const count = await Links.find().countAsync();

if (count === 0) {
  await Links.insertAsync({ url: 'https://www.meteor.js.cn' });
}

在早期版本的 Meteor 中,使用 Fibers 的异步代码可以在模块的顶级运行。顶级 await 允许编写类似的代码,这些代码无需 Fibers 即可工作。本文将介绍一些差异。

Meteor 对顶级 await 的实现试图紧密遵循规范。目前,Meteor 处理循环依赖的方式存在一些差异。

使用顶级 Await

顶级 await 可用于使用 ecmascripttypescriptcoffeescript 包的任何应用程序或包,或使用任何其他使用 reify 编译顶级 await 的构建插件的应用程序或包。通常,如果您可以使用 ECMAScript 模块,那么您也可以使用顶级 await。

在包中使用顶级 await 时,需要考虑一些额外的事项。这些内容将在本文后面介绍。

默认情况下,顶级 await 仅在服务器上启用。您可以通过将环境变量 METEOR_ENABLE_CLIENT_TOP_LEVEL_AWAIT 设置为 true 来为客户端启用它。在客户端上使用 TLA 有几个已知问题

  1. 它会破坏 /client/compatibility 中的任何文件,因为它现在将这些文件包装在一个函数中
  2. 热模块替换尚未更新以支持 TLA

异步模块

使用顶级 await 后,某些模块被视为异步模块,这会影响它们的行为。模块成为异步模块有两种方式

  1. 它使用顶级 await
  2. 它导入一个异步模块

例如,此模块 (setup.js) 将是异步的,因为它使用顶级 await

js
await setupLanguages();

此模块 (main.js) 将是同步的

js
console.log('in main.js');

但是,如果它导入使用顶级 await 的 setup.js,那么 main.js 也将变为异步的。

js
import './setup.js';

console.log('in main.js');

Require

当使用 require 加载异步模块时,它不会直接返回模块的导出,而是返回一个解析为模块导出的 Promise。

js
// resolves to the exports of init.js
const promise = require('./init.js');

如果您正在使用 require,则意味着在文件添加或删除顶级 await 时需要小心,因为您还必须更新模块所需的位置。由于如果模块依赖于异步模块,它就会变为异步模块,因此这可能会影响不仅仅是使用顶级 await 的各个模块。

如果可能,您可以使用 ecmascript 导入语法或动态导入,这样您就不必担心哪些模块是同步的或异步的。

嵌套导入

嵌套导入是指在模块根目录之外使用 import ...,例如在 if 块或函数中。

js
if (Meteor.isClient) {
  import './init-client.js';
}

export function showNotification(message) {
  import show from './notifications.js';

  show(message);
}

这是 Meteor 独有的功能,因此顶级 await 规范没有编写为支持嵌套导入。使用嵌套导入导入同步模块将继续工作,但如果用于导入异步模块,则会引发错误。在这种情况下,您可以使用 require 或动态导入用于异步模块。

在包中使用

顶级 await 仅在 Meteor 3 及更高版本中受支持。已发布的构建插件能够在较旧的 Meteor 版本中使用顶级 await,因为在发布时会捆绑运行时,但在开发中,它们需要 Meteor 3。

如果您希望确保您的包仅在支持顶级 await 的 Meteor 版本中运行,则可以使您的包使用 isobuild:top-level-await

js
Package.onUse(function (api) {
  // Do not allow this package to be used in pre-Meteor 3 apps.
  api.use("isobuild:[email protected]");
});

当导入没有延迟主模块的包时,无论包是否使用顶级 await,其工作方式都相同。即使使用 require 也是如此。这允许包添加或删除顶级 await,而不会成为重大更改。

在以下几种情况下,从包中的模块添加或删除顶级 await 可能被视为重大更改

  1. 如果从包中需要特定的模块。例如:require('meteor/zodern:aurorae/svelte.js')。当从包中导入特定模块时,require 会根据模块是异步还是同步来更改其行为。
  2. 如果需要具有延迟主模块的包。与普通包不同,如果延迟主模块是异步模块,则 require 将返回一个 Promise。更改延迟主模块是异步还是同步应被视为对包的重大更改。

模块和包执行顺序

通常,模块一次运行一个。即使在模块根目录中使用带有 Fibers 的异步代码时也是如此。但是,顶级 await 不同 - 它允许兄弟(彼此不依赖的模块)有时并行运行。这可以使应用程序加载更快,这在客户端尤其重要。但是,如果您习惯了 Meteor 使用 Fibers 的方式,这可能会导致代码以意外的顺序运行。

这也适用于包。如果包不直接或间接地相互依赖,则如果它们使用顶级 await,则能够并行加载。

那些被急切求值(在使用 api.addFiles 的包中添加,或者在没有主模块的应用程序的 imports 之外)且未直接导入的模块将继续一次运行一个,即使它们使用顶级 await 也是如此,因为这些模块通常会隐式地依赖于之前的模块。