JS 中 async/await 關鍵詞的使用詳解
在 JavaScript 異步編程領域,async/await是繼回調函數、Promise 之後的重大優化,它以同步代碼的寫法實現異步邏輯,徹底解決了回調地獄的嵌套問題,讓異步代碼的可讀性和維護性大幅提升。作為現代 JS 開發的必備技能,async/await本質是 Promise 的語法糖,但用更簡潔直觀的方式封裝了異步流程,無論是處理單個異步請求,還是串聯多個依賴異步操作,都能輕鬆應對。
async關鍵詞用於聲明一個函數為異步函數,它有兩個核心作用:一是標記函數內部存在異步操作,二是自動將函數的返回值包裝成一個 Promise 對象。即便函數內部沒有顯式返回 Promise,async 也會默認包裹一層成功狀態的 Promise, resolve 的結果就是函數的返回值。這意味着異步函數的調用結果,天然可以使用.then()和.catch()方法處理。
async 函數基礎用法代碼示例:
// 1. 普通返回值自動包裝為Promise
async function getMessage() {
return "Hello async/await"; // 等價於 return Promise.resolve("Hello async/await")
}
// 調用異步函數,返回Promise
getMessage().then(res => {
console.log(res); // 輸出:Hello async/await
});
// 2. 顯式返回Promise
async function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ code: 200, data: "模擬接口數據" });
}, 1000);
});
}
fetchData().then(res => {
console.log(res); // 1秒後輸出:{ code: 200, data: '模擬接口數據' }
});
// 3. 拋出錯誤會被Promise捕獲
async function throwError() {
throw new Error("異步操作失敗"); // 等價於 return Promise.reject(new Error("異步操作失敗"))
}
throwError().catch(err => {
console.log(err.message); // 輸出:異步操作失敗
});
而await關鍵詞必須配合async函數使用,它的作用是 “等待” Promise 的狀態變更 —— 只有當 await 後面的 Promise 變為 resolved 或 rejected 時,才會繼續執行後續代碼。這一特性讓原本需要嵌套.then()的異步流程,變成了線性的同步寫法,邏輯更清晰,調試也更方便。
需要注意的是,await只能在async函數內部使用,在普通函數或全局作用域中直接使用會報錯。
async/await 配合 Promise 示例代碼:
// 模擬兩個依賴的異步接口:先獲取用户ID,再根據ID獲取用户信息
function getUserID() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("user_1001");
}, 800);
});
}
function getUserInfo(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId) {
resolve({ id: userId, name: "林曉", age: 28, job: "前端開發" });
} else {
reject(new Error("用户ID不存在"));
}
}, 1000);
});
}
// 用async/await串聯異步操作
async function getUserDetail() {
try {
// 等待獲取用户ID
const userId = await getUserID();
console.log("獲取到的用户ID:", userId); // 800ms後輸出:獲取到的用户ID:user_1001
// 等待獲取用户信息(依賴上一步的userId)
const userInfo = await getUserInfo(userId);
console.log("用户詳情:", userInfo); // 再等待1000ms後輸出:用户詳情:{ id: 'user_1001', ... }
return userInfo;
} catch (err) {
// 捕獲所有異步操作中的錯誤
console.log("獲取用户信息失敗:", err.message);
}
}
getUserDetail();
對比傳統的 Promise 鏈式調用,async/await的優勢一目瞭然。同樣的邏輯,鏈式調用需要嵌套多個.then(),而async/await讓代碼結構扁平,閲讀起來就像同步代碼一樣自然。
Promise 鏈式調用對比代碼:
// 用Promise鏈式調用實現上述邏輯
getUserID()
.then(userId => {
console.log("獲取到的用户ID:", userId);
return getUserInfo(userId);
})
.then(userInfo => {
console.log("用户詳情:", userInfo);
})
.catch(err => {
console.log("獲取用户信息失敗:", err.message);
});
當需要並行處理多個獨立的異步操作時,async/await可以和Promise.all()配合使用,既保持代碼簡潔,又能提高執行效率。Promise.all()會等待所有異步操作完成後統一返回結果,而await可以直接等待這個組合 Promise。
並行處理異步操作示例代碼:
// 模擬三個獨立的異步接口
function fetchArticleList() {
return new Promise(resolve => setTimeout(() => resolve(["文章1", "文章2", "文章3"]), 1200));
}
function fetchCommentCount() {
return new Promise(resolve => setTimeout(() => resolve(156), 1000));
}
function fetchLikeCount() {
return new Promise(resolve => setTimeout(() => resolve(89), 900));
}
// 並行處理多個獨立異步操作
async function fetchHomeData() {
try {
// 同時發起三個異步請求,等待所有完成
const [articles, commentCount, likeCount] = await Promise.all([
fetchArticleList(),
fetchCommentCount(),
fetchLikeCount()
]);
console.log("首頁文章列表:", articles);
console.log("總評論數:", commentCount);
console.log("總點贊數:", likeCount);
} catch (err) {
console.log("獲取首頁數據失敗:", err.message);
}
}
fetchHomeData(); // 1200ms後同時輸出所有結果(取最長耗時的異步操作)
async/await的錯誤處理除了用try/catch捕獲全局錯誤,也可以給單個await表達式添加.catch()方法,針對性處理某個異步操作的異常,不影響其他流程。
單個異步操作錯誤處理示例:
async function fetchWithSingleErrorHandle() {
// 單個await的錯誤捕獲
const userList = await fetchUserList().catch(err => {
console.log("獲取用户列表失敗,使用默認數據:", err.message);
return []; // 返回默認值,避免流程中斷
});
// 後續代碼正常執行
console.log("用户列表(含默認值):", userList);
}
function fetchUserList() {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("接口請求超時")), 1000);
});
}
fetchWithSingleErrorHandle();
在實際開發中,async/await廣泛應用於接口請求、文件讀取、定時器等所有異步場景。它既保留了 Promise 的異步特性,又解決了回調地獄和鏈式調用的繁瑣問題,讓異步代碼的編寫和維護變得簡單高效。掌握async函數的聲明規則、await的等待邏輯以及錯誤處理方式,就能輕鬆應對各類異步編程需求。