async/await是什么

0 阅读5分钟

什么是 async/await?

async/await 是 ES2017(ES8)引入的处理异步操作的新语法,它是基于 Promise 的语法糖,让异步代码的写法更像同步代码,更加清晰易读。

基本概念

1. async 函数

  • 声明一个异步函数
  • 自动返回一个 Promise 对象
  • 函数内部可以使用 await
// 普通函数
function normalFunc() {
  return 'hello';
}

// async 函数
async function asyncFunc() {
  return 'hello';  // 自动包装成 Promise
}

// 调用 async 函数
asyncFunc().then(value => console.log(value)); // "hello"

2. await 表达式

  • 只能在 async 函数内部使用
  • 暂停代码执行,等待 Promise 完成
  • 返回 Promise 的结果
async function getData() {
  // await 会等待 fetch 完成,然后返回结果
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

基本用法

示例1:基本 async/await

// 模拟异步操作
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function demo() {
  console.log('开始');
  
  // 等待1秒
  await delay(1000);
  console.log('1秒后');
  
  // 再等待2秒
  await delay(2000);
  console.log('又2秒后');
  
  return '完成';
}

demo().then(result => console.log(result));
// 输出:
// 开始
// (1秒后)
// 1秒后
// (2秒后)
// 又2秒后
// 完成

示例2:处理 HTTP 请求

async function fetchUserData(userId) {
  try {
    // 等待第一个请求完成
    const userResponse = await fetch(`/api/users/${userId}`);
    const user = await userResponse.json();
    
    // 使用第一个结果发起第二个请求
    const postsResponse = await fetch(`/api/users/${userId}/posts`);
    const posts = await postsResponse.json();
    
    return { user, posts };
  } catch (error) {
    console.error('获取数据失败:', error);
    throw error;
  }
}

// 使用
fetchUserData(123)
  .then(data => console.log('用户数据:', data))
  .catch(error => console.error('错误:', error));

错误处理

1. try...catch(推荐)

async function riskyOperation() {
  try {
    const result = await someAsyncFunction();
    const anotherResult = await anotherAsyncFunction(result);
    return anotherResult;
  } catch (error) {
    // 捕获所有 await 抛出的错误
    console.error('操作失败:', error);
    
    // 可以返回默认值
    return { default: 'value' };
    
    // 或者重新抛出
    // throw new Error('包装后的错误');
  } finally {
    console.log('无论成功失败都会执行');
  }
}

2. catch() 方法

async function example() {
  const result = await someAsyncFunction()
    .catch(error => {
      console.error('捕获错误:', error);
      return '默认值'; // 提供降级值
    });
  
  console.log('结果:', result);
}

实际应用场景

场景1:顺序执行异步操作

// 之前使用 Promise.then 的链式调用
function oldWay() {
  fetchUser()
    .then(user => fetchOrders(user.id))
    .then(orders => fetchProducts(orders[0].id))
    .then(product => console.log(product))
    .catch(error => console.error(error));
}

// 使用 async/await 更清晰
async function newWay() {
  try {
    const user = await fetchUser();
    const orders = await fetchOrders(user.id);
    const product = await fetchProducts(orders[0].id);
    console.log(product);
  } catch (error) {
    console.error(error);
  }
}

场景2:并行执行异步操作

async function parallelOperations() {
  // 并行执行,互不依赖
  const [user, products, cart] = await Promise.all([
    fetchUser(),
    fetchProducts(),
    fetchCart()
  ]);
  
  // 所有数据都获取后再处理
  return { user, products, cart };
}

// 错误处理的并行操作
async function parallelWithErrorHandling() {
  try {
    const [userResult, productResult] = await Promise.allSettled([
      fetchUser(),
      fetchProducts()
    ]);
    
    const user = userResult.status === 'fulfilled' 
      ? userResult.value 
      : null;
    
    const products = productResult.status === 'fulfilled'
      ? productResult.value
      : [];
    
    return { user, products };
  } catch (error) {
    console.error('其他错误:', error);
  }
}

场景3:循环中的异步操作

// ❌ 错误的方式 - 没有正确使用 await
async function wrongLoop() {
  const urls = ['url1', 'url2', 'url3'];
  const results = [];
  
  urls.forEach(async (url) => {
    const data = await fetch(url); // 这不会按顺序执行
    results.push(data);
  });
  
  console.log(results); // 这里可能还是空的
}

// ✅ 正确的方式1 - 顺序执行
async function sequentialLoop() {
  const urls = ['url1', 'url2', 'url3'];
  const results = [];
  
  for (const url of urls) {
    const data = await fetch(url); // 等待一个完成再下一个
    results.push(data);
  }
  
  return results;
}

// ✅ 正确的方式2 - 并行执行
async function parallelLoop() {
  const urls = ['url1', 'url2', 'url3'];
  
  // 所有请求并行执行
  const promises = urls.map(url => fetch(url));
  const results = await Promise.all(promises);
  
  return results;
}

高级用法

1. 立即执行 async 函数

// IIFE 模式(立即调用的函数表达式)
(async function() {
  try {
    const data = await fetchData();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
})();

// 箭头函数版本
(async () => {
  const result = await someAsyncTask();
  console.log(result);
})();

2. 在类中使用 async 方法

class ApiClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }
  
  async get(endpoint) {
    const response = await fetch(`${this.baseUrl}/${endpoint}`);
    return response.json();
  }
  
  async post(endpoint, data) {
    const response = await fetch(`${this.baseUrl}/${endpoint}`, {
      method: 'POST',
      body: JSON.stringify(data)
    });
    return response.json();
  }
}

// 使用
const client = new ApiClient('https://api.example.com');
const users = await client.get('users');

3. 多个错误处理策略

async function complexOperation() {
  // 尝试主方法
  let data = await fetchPrimary()
    .catch(() => null); // 失败返回 null
  
  // 如果主方法失败,尝试备用方法
  if (!data) {
    data = await fetchBackup()
      .catch(error => {
        console.warn('备用方法也失败:', error);
        return getDefaultData(); // 返回默认数据
      });
  }
  
  // 处理数据,可能抛出错误
  try {
    return processData(data);
  } catch (error) {
    // 处理处理过程中的错误
    return handleProcessingError(error);
  }
}

注意事项和最佳实践

1. 不要滥用 await

// ❌ 不好的写法 - 不必要的串行
async function badPractice() {
  const user = await getUser();      // 等待
  const orders = await getOrders();  // 等待
  const settings = await getSettings(); // 等待
  // 这三个请求是独立的,但被串行执行了
}

// ✅ 好的写法 - 并行执行
async function goodPractice() {
  const [user, orders, settings] = await Promise.all([
    getUser(),
    getOrders(),
    getSettings()
  ]);
  // 三个请求并行执行
}

2. 错误处理要全面

async function safeFunction() {
  try {
    // 主要逻辑
    const result = await mainOperation();
    
    // 可能抛出错误的后续操作
    const processed = processResult(result);
    
    return processed;
  } catch (error) {
    // 根据错误类型处理
    if (error instanceof NetworkError) {
      return retryOperation();
    } else if (error instanceof ValidationError) {
      return getDefaultValue();
    } else {
      // 记录并重新抛出未知错误
      logError(error);
      throw error;
    }
  }
}

3. async/await 和 Promise 混合使用

async function mixedUsage() {
  // 在 async 函数中也可以使用 .then/.catch
  const data = await fetchData()
    .then(response => transformData(response))
    .catch(error => {
      console.error('获取数据失败:', error);
      return getDefaultData();
    });
  
  // 也可以返回新的 Promise
  return new Promise((resolve, reject) => {
    // 一些回调式的异步操作
    someCallbackFunction((err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
}

常见问题解答

Q1: async 函数总是返回 Promise 吗?

是的,即使你返回一个普通值,也会被自动包装成 Promise。

Q2: await 只能在 async 函数中使用吗?

是的,在普通函数中使用会报语法错误。

Q3: 顶层 await 可以用吗?

在 ES2022+ 中可用,但在模块中需要特定配置:

// 在模块中可以直接使用
const response = await fetch('/api/data');

Q4: async/await 会阻塞线程吗?

不会,await 只是暂停 async 函数的执行,不会阻塞 JavaScript 主线程。

总结

async/await 的优点:

  1. 代码更清晰:像写同步代码一样写异步代码
  2. 错误处理更简单:可以使用 try...catch
  3. 调试更方便:错误堆栈更清晰
  4. 条件判断更直观:可以在 if、for 等语句中直接使用

适用场景:

  • 多个有依赖关系的异步操作
  • 需要复杂错误处理的异步流程
  • 希望代码更易读和维护的项目

async/await 并没有取代 Promise,而是让 Promise 的使用更加优雅。两者结合使用可以写出更健壮的异步代码。