Skip to content

顶层等待

¥Top Level Await

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

¥Top level await (TLA) allows you to use await in the top level of a module or file instead of only in async functions. One way to view it is as if every file runs inside an async function.

以下是在服务器上使用顶层 await 的示例。加载此文件时,await 将使模块等待计数,然后运行模块其余部分的代码。

¥Here is an example of using top level await on the server. When this file is loaded, the await will cause the module to wait for the count before the code in the rest of the module is run.

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://meteor.com' });
}

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

¥In previous versions of Meteor, async code using fibers could be run in the top level of a module. Top level await allows writing similar code that works without fibers. There are a few differences that this article will cover.

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

¥Meteor's implementation of top level await tries to closely follow the specification. There currently are some differences with how Meteor handles circular dependencies.

使用顶层等待

¥Using Top Level Await

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

¥Top level await can be used in any app or package that uses the ecmascript, typescript, or coffeescript packages, or that uses any other build plugin that compiles top level await using reify. Generally, if you can use ECMAScript modules, then you can also use top level await.

在包中使用顶层 await 时需要考虑一些额外事项。它们将在本文后面介绍。

¥There are some extra considerations when using top level await in packages. They are covered later in this article.

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

¥Top level await is only enabled by default on the server. You can enable it for the client by setting the env var METEOR_ENABLE_CLIENT_TOP_LEVEL_AWAIT to true. There are a couple known issues with using TLA on the client:

  1. 它破坏了 /client/compatibility 中的任何文件,因为它现在将这些文件封装在一个函数中

    ¥It breaks any files in /client/compatibility since it now wraps those files in a function

  2. 热模块替换尚未更新以与 TLA 配合使用

    ¥Hot module replacement has not been updated to work with TLA

异步模块

¥Async Modules

使用顶层 await,某些模块被视为异步,这会影响它们的行为。模块可以通过两种方式成为异步模块:

¥With top level await, some modules are considered async, which affects how they behave. There are two ways a module can become an async module:

  1. 它使用顶层 await

    ¥It uses top level await

  2. 它导入了一个异步模块

    ¥It imports a module that is async

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

¥For example, this module (setup.js) would be async because it uses top level await:

js
await setupLanguages();

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

¥This module (main.js) would be sync:

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

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

¥However, if it imports setup.js which does use top level await, then main.js also becomes async.

js
import './setup.js';

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

需要

¥Require

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

¥When using require to load an async module, instead of directly returning a module's exports, it will return a promise that resolves to the module's exports.

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

如果你使用的是 require,这确实意味着你在文件中添加或删除顶层 await 时需要小心,因为你还必须更新模块所需的位置。由于如果模块依赖于异步模块,它将变为异步,这可能会影响使用顶层 await 的单个模块。

¥If you are using require, this does mean you need to be careful when adding or removing top level await in a file since you also have to update where the module is required. Since a module becomes async if it depends on an async module, this could affect more than just the individual modules using top level await.

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

¥When possible, you can use ecmascript import syntax or dynamic imports instead so you don't have to worry about which modules are sync or async.

嵌套导入

¥Nested Imports

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

¥Nested imports refer to using import ... outside of the root of a module, for example in an if block or a function.

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

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

  show(message);
}

这是 Meteor 独有的功能,因此顶层 await 规范不是为与嵌套导入一起使用而编写的。使用嵌套导入来导入同步模块仍然有效,但如果用于导入异步模块,则会抛出错误。在这些情况下,你可以使用 require 或动态导入异步模块。

¥This is a feature unique to Meteor, so the top level await specification wasn't written to work with nested imports. Using nested imports to import a sync module continues to work, but it will throw an error if used to import an async module. You can use require or dynamic imports for async modules in these situations.

在包中使用

¥Using in Packages

从 Meteor 3 开始,才支持顶层 await。已发布的构建插件能够在旧版 Meteor 中使用顶层 await,因为运行时在发布时已打包,但在开发中它们需要 Meteor 3。

¥Top level await is only supported starting in Meteor 3. Published build plugins are able to use top level await in older Meteor versions since the runtime is bundled when they are published, though in development they require Meteor 3.

如果你想确保你的包仅在支持顶层等待的 Meteor 版本中运行,你可以让你的包使用 isobuild:top-level-await

¥If you want to ensure your package only runs in versions of Meteor that support top level await, you can have your package use 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:top-level-await@3.0.0");
});

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

¥When importing a package that does not have a lazy main module, it will work the same whether a package uses top level await or not. This is true even when using require. This allows packages to add or remove top level await without it being a breaking change.

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

¥There are a couple cases where adding or removing top level await from a module in a package could be considered a breaking change:

  1. 如果需要从包中获取特定模块。例如:require('meteor/zodern:aurorae/svelte.js')。当从包中导入特定模块时,require 会根据模块是否异步更改其行为。

    ¥If specific modules are require'd from a package. For example: require('meteor/zodern:aurorae/svelte.js'). When importing a specific module from a package, require changes its behavior based on if the module is async or not.

  2. 如果需要具有惰性主模块的包。与普通包不同,如果惰性主模块是异步模块,require 将返回一个 promise。更改惰性主模块是否异步应被视为包的重大更改。

    ¥If a package that has lazy main modules is require'd. Unlike normal packages, require will return a promise if the lazy main module is an async module. Changing if the lazy main module is async or not should be considered a breaking change for the package.

模块和包执行顺序

¥Module and Package Execution Order

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

¥Normally, modules are run one at a time. This was even true when using async code with fibers in the root of a module. However, top level await is different - it allows siblings (modules that do not depend on each other) to sometimes run in parallel. This can allow the app to load faster, which is especially important on the client. However, this could cause code to run in an unexpected order if you are used to how Meteor worked with fibers.

这也适用于包。如果使用顶层 await,则不直接或间接相互依赖的包可以并行加载。

¥This is also applies to packages. Packages that do not directly or indirectly depend on each other are able to load in parallel if they use top level await.

预评估的模块(在带有 api.addFiles 的包中添加,或在没有主模块的应用中 imports 之外添加)并且未直接导入的模块将继续一次运行一个,即使它们使用顶层 await,因为这些模块通常隐式依赖于以前的模块。

¥Modules that are eagerly evaluated (added in packages with api.addFiles, or outside of imports in apps that do not have a main module) and not directly imported continue to run one at a time, even if they use top level await, since it is common for these modules to implicitly depend on the previous modules.