顶级 Await
顶级 await (TLA) 允许您在模块或文件的顶级使用 await
,而不是仅在异步函数中使用。一种看待它的方式是,好像每个文件都在一个 async
函数内运行。
以下是在服务器上使用顶级 await 的示例。当加载此文件时,await
将导致模块在运行模块其余部分中的代码之前等待计数。
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 可用于使用 ecmascript
、typescript
或 coffeescript
包的任何应用程序或包,或使用任何其他使用 reify 编译顶级 await 的构建插件的应用程序或包。通常,如果您可以使用 ECMAScript 模块,那么您也可以使用顶级 await。
在包中使用顶级 await 时,需要考虑一些额外的事项。这些内容将在本文后面介绍。
默认情况下,顶级 await 仅在服务器上启用。您可以通过将环境变量 METEOR_ENABLE_CLIENT_TOP_LEVEL_AWAIT
设置为 true
来为客户端启用它。在客户端上使用 TLA 有几个已知问题
- 它会破坏
/client/compatibility
中的任何文件,因为它现在将这些文件包装在一个函数中 - 热模块替换尚未更新以支持 TLA
异步模块
使用顶级 await 后,某些模块被视为异步模块,这会影响它们的行为。模块成为异步模块有两种方式
- 它使用顶级 await
- 它导入一个异步模块
例如,此模块 (setup.js
) 将是异步的,因为它使用顶级 await
await setupLanguages();
此模块 (main.js
) 将是同步的
console.log('in main.js');
但是,如果它导入使用顶级 await 的 setup.js
,那么 main.js
也将变为异步的。
import './setup.js';
console.log('in main.js');
Require
当使用 require
加载异步模块时,它不会直接返回模块的导出,而是返回一个解析为模块导出的 Promise。
// resolves to the exports of init.js
const promise = require('./init.js');
如果您正在使用 require
,则意味着在文件添加或删除顶级 await 时需要小心,因为您还必须更新模块所需的位置。由于如果模块依赖于异步模块,它就会变为异步模块,因此这可能会影响不仅仅是使用顶级 await 的各个模块。
如果可能,您可以使用 ecmascript 导入语法或动态导入,这样您就不必担心哪些模块是同步的或异步的。
嵌套导入
嵌套导入是指在模块根目录之外使用 import ...
,例如在 if 块或函数中。
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
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 可能被视为重大更改
- 如果从包中需要特定的模块。例如:
require('meteor/zodern:aurorae/svelte.js')
。当从包中导入特定模块时,require
会根据模块是异步还是同步来更改其行为。 - 如果需要具有延迟主模块的包。与普通包不同,如果延迟主模块是异步模块,则
require
将返回一个 Promise。更改延迟主模块是异步还是同步应被视为对包的重大更改。
模块和包执行顺序
通常,模块一次运行一个。即使在模块根目录中使用带有 Fibers 的异步代码时也是如此。但是,顶级 await 不同 - 它允许兄弟(彼此不依赖的模块)有时并行运行。这可以使应用程序加载更快,这在客户端尤其重要。但是,如果您习惯了 Meteor 使用 Fibers 的方式,这可能会导致代码以意外的顺序运行。
这也适用于包。如果包不直接或间接地相互依赖,则如果它们使用顶级 await,则能够并行加载。
那些被急切求值(在使用 api.addFiles
的包中添加,或者在没有主模块的应用程序的 imports
之外)且未直接导入的模块将继续一次运行一个,即使它们使用顶级 await 也是如此,因为这些模块通常会隐式地依赖于之前的模块。