myfreax

JavaScript 顶层 await

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

JavaScript 顶层 await
JavaScript 顶层 await

在本教程中,您将了解 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 模块完成,然后再运行其余代码。

内容导航