Appearance
顶层等待
¥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 3 之前,使用纤程的异步代码可以在模块的顶层运行。顶层 await 允许类似代码在没有纤程的情况下工作。本文将介绍一些差异。
¥Before Meteor 3, async code using fibers could run at the top level of a module. Top-level await allows similar code to work without fibers. This article will cover a few differences.
Meteor 的顶层 await 实现试图紧密遵循规范。但是,目前 Meteor 处理循环依赖的方式存在一些差异。
¥Meteor's implementation of top-level await tries to closely follow the specification. However, there are currently some differences in how Meteor handles circular dependencies.
使用顶层等待
¥Using Top Level Await
顶层 await 可用于使用 ecmascript
、typescript
或 coffeescript
包的任何应用或包中,或用于使用 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:
它破坏了
/client/compatibility
中的任何文件,因为它现在将这些文件封装在一个函数中¥It breaks any files in
/client/compatibility
since it now wraps those files in a function热模块替换尚未更新以与 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:
它使用顶层 await
¥It uses top-level await
它导入了一个异步模块
¥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 feature is 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.
如果你想确保你的包仅在支持顶层 await 的 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 of cases where adding or removing top-level await from a module in a package could be considered a breaking change:
如果需要从包中获取特定模块。例如:
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.如果需要具有惰性主模块的包。与普通包不同,如果惰性主模块是异步模块,
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 works with fibers.
这也适用于包。如果使用顶层 await,则不直接或间接相互依赖的包可以并行加载。
¥This also applies to packages. Packages that do not directly or indirectly depend on each other can 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.