JavaScript 中的 async/await 是什么?和 promise 有什么差别?
2024年3月30日
在之前的文章中
我们分别讨论过事件循环 (Event Loop)、异步(非同步) 和 Promise 的概念,而在这篇文章中,我们将深入探讨 async/await
是什么?以及它与 Promise 的差异。
async/await
是什么?
在 JavaScript 中,async/await
是一种让异步(非同步)操作更容易理解和管理的语法。它建立在 Promise 的基础上,但提供了更简洁、更直观的方式来处理异步操作。
以下我们先把 async
和 await
拆开来看:
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 levelawait
)
顶层 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");
在这个例子中:
- 使用
async
关键字定义一个异步函式,该函式会返回一个 Promise 对象。 - 在异步函式中,使用
await
等待 Promise 的完成,并直接返回结果。 - 使用
try...catch
捕获错误,使得错误处理更加方便和直观。
可以看到,使用 async/await
后,代码变得更加清晰和易于理解。
async/await
与 Promise 的差别?
async/await
和 Promise 都是用于处理异步操作的方式,但它们有以下一些差异:
- 语法:
async/await
提供了更简洁、更直观的语法,使得异步代码更易读和维护。Promise 则需要使用then
和catch
方法来处理结果和错误,语法上较为冗长。 - 错误处理: 在
async/await
中,可以直接使用try...catch
来捕获错误,而在 Promise 中需要使用catch
方法。 - 代码流程:
async/await
可以使异步代码看起来更像同步代码,更容易阅读和理解。Promise 的代码流程则较为不连贯。