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的等待邏輯以及錯誤處理方式,就能輕鬆應對各類異步編程需求。