在本教程中,您将了解 JavaScript 顶层 await 及其用例。
JavaScript 顶层 await 简介
ES2020 引入顶层 await 特性,允许模块像 async
函数一样运行。导入顶层 await 模块的模块将会等待顶级 await 模块运行之后再运行其余代码。
为了更好地理解顶层 await 功能,我们将举一个例子:
在此示例中,我们有三个文件:index.html
、app.mjs
和 user.mjs
:
index.html
使用app.mjs
文件。app.mjs
导入user.mjs
文件。user.mjs
从 API 获取 JSON 格式的用户数据
这是使用 app.mjs 模块的 index.html
文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript Top-Level Await Demo</title>
</head>
<body>
<div class="container"></div>
<script type="module" src="app.mjs"></script>
</body>
</html>
user.mjs
文件如下:
let users;
(async () => {
const url = 'https://jsonplaceholder.typicode.com/users';
const response = await fetch(url);
users = await response.json();
})();
export { users };
user.mjs
模块使用 Fetch API 从 API 获取 JSON 格式的用户并将其导出。
因为我们只能在 async
函数内部使用 await
关键字(在 ES2020 之前),所以我们需要将 API 调用封装在立即调用的异步函数表达式 IIFE 中。
app.mjs
模块如下:
import { users } from './user.mjs';
function render(users) {
if (!users) {
throw 'The user list is not available';
}
const list = users
.map((user) => {
return `<li> ${user.name}(<a href="email:${user.email}">${user.email}</a>)</li>`;
})
.join('');
return `<ol>${list}</ol>`;
}
const container = document.querySelector('.container');
try {
container.innerHTML = render(users);
} catch (e) {
container.innerHTML = e;
}
代码如何运行。
首先,从 user.mjs
模块导入 users
:
import { users } from './user.mjs';
其次,创建一个 render()
函数将用户列表使用 HTML 有序列表进行展示:
function render(users) {
if (!users) {
throw 'The user list is not available.';
}
const list = users
.map((user) => {
return `<li> ${user.name}(<a href="email:${user.email}">${user.email}</a>)</li>`;
})
.join('');
return `<ol>${list}</ol>`;
}
第三,将用户列表添加到具有类 .container
的 HTML 元素 :
const container = document.querySelector('.container');
try {
container.innerHTML = render(users);
} catch (e) {
container.innerHTML = e;
}
如果您打开 index.html
,您将看到以下消息:
The user list is not available.
主要流程如下:
在这个流程中:
- 首先,
app.mjs
导入user.mjs
模块。 - 其次,
user.mjs
模块执行并进行 API 调用。 - 第三,当第二步仍在进行时,
app.mjs
开始从user.mjs
模块导入users
的数据并使用。
由于第 2 步尚未完成,因此 users
变量为 undefined
。因此,您在页面上看到了错误消息。
解决方法
要解决此问题,您可以从 user.mjs
模块导出一个 Promise
并等待 API 调用完成,然后再使用其结果。
新版本的 user.mjs
模块如下:
let users;
export default (async () => {
const url = 'https://jsonplaceholder.typicode.com/users';
const response = await fetch(url);
users = await response.json();
})();
export { users };
在这个新版本中,user.mjs
模块将 users
导出并 Promise
作为默认导出。
在 app.mjs
模块从 user.mjs
模块导入 promise
和 users
并调用 promise
的 then()
方法:
import promise, { users } from './user.mjs';
function render(users) {
if (!users) {
throw 'The user list is not available.';
}
let list = users
.map((user) => {
return `<li> ${user.name}(<a href="email:${user.email}">${user.email}</a>)</li>`;
})
.join(' ');
return `<ol>${list}</ol>`;
}
promise.then(() => {
let container = document.querySelector('.container');
try {
container.innerHTML = render(users);
} catch (error) {
container.innerHTML = error;
}
});
代码是如何运行的。
首先,从 user.mjs
模块导入 promise
和 users
:
import promise, { users } from './user.mjs';
其次,调用 promise 的 then()
方法并等待 API 调用完成,然后使用其结果:
promise.then(() => {
let container = document.querySelector('.container');
try {
container.innerHTML = render(users);
} catch (error) {
container.innerHTML = error;
}
});
现在,如果您打开 index.html
,您将看到一个用户列表。但是,您需要在使用模块时知道等待 Promise 。
ES2022 引入顶层 await 模块来解决此问题。
顶层 await
首先,将 user.mjs
修改为以下内容:
const url = 'https://jsonplaceholder.typicode.com/users';
const response = await fetch(url);
let users = await response.json();
export { users };
在此模块中,您可以使用 await
关键字而无需在函数放置 async
关键字。
然后,从 user.mjs
模块中导入用户并使用它:
import { users } from './user.mjs';
function render(users) {
if (!users) {
throw 'The user list is not available.';
}
let list = users
.map((user) => {
return `<li> ${user.name}(<a href="email:${user.email}">${user.email}</a>)</li>`;
})
.join(' ');
return `<ol>${list}</ol>`;
}
let container = document.querySelector('.container');
try {
container.innerHTML = render(users);
} catch (error) {
container.innerHTML = error;
}
在这种情况下,app.mjs
模块将在执行其主体之前等待user.mjs
模块完成。
JavaScript 顶层 await 用例
你什么时候使用 JavaScript 顶层 await 。
动态依赖路径
const words = await import(`/i18n/${navigator.language}`);
在这个例子中,顶层 await 允许模块使用运行时值来决定依赖关系,这对以下场景很有用:
- i18n 国际化
- 开发 / 生产环境分离。
依赖回退
在这种情况下,您可以使用顶层 await 从服务器 (cdn1) 加载模块。如果失败,您可以从备份服务器 (cdn2) 加载它:
let module;
try {
module = await import('https://cdn1.com/module');
} catch {
module = await import('https://cdn2.com/module');
}
结论
- 顶层 await 模块就像一个
async
函数。 - 当模块导入顶层 await 模块时,它会等待顶层 await 模块完成,然后再运行其余代码。