在本教程中,您将了解 JavaScript 顶层 await 及其用例。

JavaScript 顶层 await 简介

ES2020 引入顶层 await 特性,允许模块像 async 函数一样运行。导入顶层 await 模块的模块将会等待顶级 await 模块运行之后再运行其余代码。

为了更好地理解顶层 await 功能,我们将举一个例子:

在此示例中,我们有三个文件:index.htmlapp.mjsuser.mjs

  • index.html 使用 app.mjs 文件。
  • app.mjs 导入 user.mjs 文件。
  • user.mjsAPI 获取 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 模块导入 promiseusers 并调用 promisethen() 方法:

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 模块导入 promiseusers

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 模块完成,然后再运行其余代码。