JavaScript 顶层 await

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

JavaScript 顶层 await 简介

ES2020 引入了顶层 await 功能,允许模块的行为类似于 async 函数。导入顶层 await 模块的模块将等待它加载,然后再评估其主体。

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

在这个例子中,我们将有三个文件:index.htmlapp.mjsuser.mjs

以下是使用 app.mjs 模块的索引文件

<!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>Code language: HTML, XML (xml)

以下显示了 user.mjs 文件

let users;

(async () => {
  const url = 'https://jsonplaceholder.typicode.com/users';
  const response = await fetch(url);
  users = await response.json();
})();

export { users };Code language: JavaScript (javascript)

user.mjs 模块使用 fetch API 从 API 中获取 JSON 格式的用户并导出它。

因为我们只能在 async 函数(ES2020 之前)中使用 await 关键字,所以我们需要将 API 调用包装在一个立即调用的异步函数表达式 (IIAFE) 中。

以下是 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;
}
Code language: JavaScript (javascript)

它是如何工作的。

首先,从 user.mjs 模块导入 users

import { users } from './user.mjs';Code language: JavaScript (javascript)

其次,创建一个 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>`;
}Code language: JavaScript (javascript)

第三,将用户列表添加到具有类 .container 的 HTML 元素中

const container = document.querySelector('.container');
try {
  container.innerHTML = render(users);
} catch (e) {
  container.innerHTML = e;
}Code language: JavaScript (javascript)

如果你打开 index.html,你会看到以下消息

The user list is not available.Code language: PHP (php)

以下显示了主要流程

JavaScript top-level await example

在这个流程中

  • 首先,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 };Code language: JavaScript (javascript)

在这个新版本中,user.mjs 模型导出 users 和一个 Promise 作为默认导出。

app.mjs 中,从 user.mjs 文件导入 promiseusers,并按如下方式调用 then() 方法:promise

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;
  }
});
Code language: JavaScript (javascript)

它是如何工作的。

首先,从 user.mjs 模块导入 promiseusers

import promise, { users } from './user.mjs';Code language: JavaScript (javascript)

其次,调用 promise 的 then() 方法,等待 API 调用完成以使用其结果

promise.then(() => {
  let container = document.querySelector('.container');
  try {
    container.innerHTML = render(users);
  } catch (error) {
    container.innerHTML = error;
  }
});Code language: JavaScript (javascript)

现在,如果你打开 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 };Code language: JavaScript (javascript)

在这个模块中,你可以使用 await 关键字,而无需将语句放在 async 函数中。

其次,从 user.mjs 模块导入 users 并使用它

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;
}
Code language: JavaScript (javascript)

在这种情况下,app.mjs 模块将在执行其主体之前等待 user.mjs 模块完成。

JavaScript 顶层 await 用例

什么时候使用顶层 await?以下是一些用例。

动态依赖路径

const words = await import(`/i18n/${navigator.language}`);Code language: JavaScript (javascript)

在这个例子中,顶层 await 允许模块使用运行时值来决定依赖项,这对于以下场景很有用

  • 国际化 (i18n)
  • 开发/生产环境拆分。

依赖项回退

在这种情况下,您可以使用顶层 await 从服务器(cdn1)加载模块。如果失败,您可以从备份服务器(cdn2)加载它

let module;
try {
  module = await import('https://cdn1.com/module');
} catch {
  module = await import('https://cdn2.com/module');
}Code language: JavaScript (javascript)

概要

  • 顶层 await 模块的行为类似于 async 函数。
  • 当模块导入顶层 await 模块时,它会等待顶层 await 模块完成,然后再评估其主体。
本教程是否有帮助?