JavaScript 中的 async/await 是什么?和 promise 有什么差别?

2024年3月30日

💎 加入 E+ 成長計畫 與超過 350+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

在之前的文章中

我们分别讨论过事件循环 (Event Loop)、异步(非同步) 和 Promise 的概念,而在这篇文章中,我们将深入探讨 async/await 是什么?以及它与 Promise 的差异。

async/await 是什么?

在 JavaScript 中,async/await 是一种让异步(非同步)操作更容易理解和管理的语法。它建立在 Promise 的基础上,但提供了更简洁、更直观的方式来处理异步操作。

以下我们先把 asyncawait 拆开来看:

async 语法

使用 async 关键字声明的函式为异步函式,异步函式会返回一个 Promise 物件,而非直接返回函式执行的结果。让我们透过范例来了解:

  • 下方普通函式 f1() 直接返回字串 "Hello! ExplainThis!"
// 普通函式
function f1() {
  return "Hello! ExplainThis!";
}

f1(); // 输出: "Hello! ExplainThis!"
  • 下方代码中, async function f2() {...} 定义了一个名为 f2 的异步函式,该函式返回字串 "Hello! ExplainThis!",并将其封装在一个 Promise 物件中。
// 异步函式
async function f2() {
  return "Hello! ExplainThis!";
}

f2(); // 输出: Promise {<fulfilled>: 'Hello! ExplainThis!'}

上方代码写法跟下方写法其实是相同的,因为使用 async 时,会自动将回传值包装在一个 Promise 物件当中。

// 异步函式
function f3() {
  return Promise.resolve("Hello! ExplainThis!");
}

f3(); // 输出: Promise {<fulfilled>: 'Hello! ExplainThis!'}

由于 async 函式总是返回一个 Promise 对象,如果要获取该 Promise 的解析值,可以使用 .then() 方法:

async function f2() {
  return "Hello! ExplainThis!";
}

f2().then((result) => {
  console.log(result); // "Hello! ExplainThis!"
});

await 语法

await 是一个运算子,用于等待一个 Promise 完成或拒绝。它通常与 async 函式一起使用,因为只有在 async 函式内部或模组的顶层,才能使用 await

当使用 await 时,程式会暂停执行该 async 函式,直到 await 等待的 Promise 完成并回传结果后,才会继续往下执行。让我们透过下方范例来了解:

async function getData() {
  // await 等待 fetch 这个非同步函式返回一个 Promise 并解析它
  const res = await fetch("https://example.com/data");

  // await 等待上一步的 Promise 解析后,再解析它的 JSON 资料
  const data = await res.json();

  // 前面两步都完成后,才会执行这一行并印出资料
  console.log(data);
}

getData();

使用 await 要注意的几点

  • 在非 async 函式中使用 await 会报 SyntaxError 的错误
function f() {
  let promise = Promise.resolve("Hello! ExplainThis!");
  let result = await promise;
}

// Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
  • 顶层 await (Top level await)

顶层 await 是一个在 JavaScript 中引入的新语法功能,它允许在模组的最顶层使用 await 关键字。在 ES 模组中,原本只有 async 函式内部才能使用 await。但是使用顶层 await 后,就可以直接在模组顶层使用 await 了。

例如,如果想在某个模组中获取远端资源,以前需要这样写:

import getData from "./getData.js";

let data;

getData().then((result) => {
  data = result;
  // ...使用data
});

有了顶层 await (Top level await),你就可以这样更直接地写:

const data = await getData();
// ...使用data

如何使用 async/await

使用 async/await 可以将异步代码写成类似同步的形式,使其更易读、且更易维护。让我们先看一个使用 Promise 写的 getData 函式范例:

我们先来看用 Promise 来写一个 getData 函式的例子:

function getData(url) {
  return new Promise((resolve, reject) => {
    fetch(url)
      .then((res) => res.json())
      .then((data) => resolve(data))
      .catch((error) => reject(error));
  });
}

getData("https://example.com/data")
  .then((data) => console.log(data))
  .catch((error) => console.error(error));

在这个例子中,getData 函式使用 Promise 来处理异步操作。我们需要使用 .then().catch() 方法来获取结果或错误。

现在,我们使用 async/await 来重写 getData 函式:

async function getData(url) {
  try {
    const res = await fetch(url);
    const data = await res.json();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

getData("https://example.com/data");

在这个例子中:

  1. 使用 async 关键字定义一个异步函式,该函式会返回一个 Promise 对象。
  2. 在异步函式中,使用 await 等待 Promise 的完成,并直接返回结果。
  3. 使用 try...catch 捕获错误,使得错误处理更加方便和直观。

可以看到,使用 async/await 后,代码变得更加清晰和易于理解。

async/await 与 Promise 的差别?

async/await 和 Promise 都是用于处理异步操作的方式,但它们有以下一些差异:

  1. 语法: async/await 提供了更简洁、更直观的语法,使得异步代码更易读和维护。Promise 则需要使用 thencatch 方法来处理结果和错误,语法上较为冗长。
  2. 错误处理: 在 async/await 中,可以直接使用 try...catch 来捕获错误,而在 Promise 中需要使用 catch 方法。
  3. 代码流程: async/await 可以使异步代码看起来更像同步代码,更容易阅读和理解。Promise 的代码流程则较为不连贯。
🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們