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 的程式碼流程則較為不連貫。