为什么要使用 async/await?

在 JavaScript 中,许多操作(如:获取网络数据、读取文件、数据库操作等)需要时间来完成。这些操作不能立即返回结果,我们称之为异步操作

在 async/await 出现之前,我们主要使用回调函数和 Promise 来处理异步操作,但它们有一些缺点:

回调地狱 (Callback Hell)

嵌套多层回调函数,代码难以阅读和维护:

getData(function(a) {
  getMoreData(a, function(b) {
    getMoreData(b, function(c) {
      // 更多回调…
    });
  });
});

Promise 的改进

Promise 解决了回调地狱的问题,但代码仍然不够直观:

getData()
  .then(a => getMoreData(a))
  .then(b => getMoreData(b))
  .then(c => {
    // 处理最终结果
  })
  .catch(error => {
    console.error(error);
  });

async/await 是 JavaScript 异步编程的终极解决方案,它让异步代码看起来和同步代码一样直观,同时保留了异步操作的非阻塞特性。

理解 async 函数

async 关键字用于声明一个函数是异步的:

// 声明一个异步函数
async function fetchData() {
  // 函数体
}

重要特性

1. async 函数总是返回一个 Promise:无论函数返回什么值,它都会被自动包装在一个 Promise 中

2. 如果函数返回一个值,它会成为 Promise 的 resolved 值

3. 如果函数抛出错误,它会成为 Promise 的 rejected 值

// 示例 1: 返回普通值
async function getNumber() {
  return 42; // 等价于 Promise.resolve(42)
}

// 示例 2: 返回 Promise
async function getUser() {
  return fetch(‘/api/user’); // 返回一个 Promise
}

// 示例 3: 抛出错误
async function fail() {
  throw new Error(“出错了!”); // 等价于 Promise.reject(error)
}

理解 await 表达式

await 关键字只能在 async 函数内部使用,它的作用是暂停 async 函数的执行,等待一个 Promise 完成(resolved 或 rejected),然后继续执行 async 函数。

await 的行为

1. 当遇到 await 时,JavaScript 引擎会暂停 async 函数的执行

2. 等待 await 后面的 Promise 完成

3. 如果 Promise 成功完成(resolved),返回 resolved 的值

4. 如果 Promise 被拒绝(rejected),抛出拒绝的原因(可以使用 try/catch 捕获)

async function fetchUser() {
  // 等待 fetch 完成,然后将响应赋值给 response
  const response = await fetch(‘https://api.example.com/user’);

  // 等待解析 JSON 完成,然后将结果赋值给 data
  const data = await response.json();

  return data;
}

await 执行流程可视化

1
调用 fetchUser() 异步函数
2
遇到第一个 await,暂停执行,发起网络请求
3
网络请求完成,获得响应对象
4
遇到第二个 await,暂停执行,解析JSON数据
5
JSON解析完成,获得数据对象
6
返回最终结果

错误处理

处理 async/await 中的错误有两种主要方式:

方式 1: try/catch 块

async function loadData() {
  try {
    const response = await fetch(‘https://api.example.com/data’);
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error(‘请求失败:’, error);
  }
}

方式 2: 使用 catch 方法

async function loadData() {
  const response = await fetch(‘https://api.example.com/data’)
    .catch(error => console.error(‘请求失败:’, error));

  if (!response) return; // 如果出错则终止

  const data = await response.json();
  console.log(data);
}

重要提示

1. 如果没有错误处理,async 函数中抛出的错误会导致返回的 Promise 被拒绝

2. 总是处理异步操作中的潜在错误,避免程序崩溃

async/await 最佳实践

1. 避免不必要的顺序等待

如果多个操作之间没有依赖关系,可以并行执行:

// 顺序执行 – 慢 ❌
async function getSequential() {
  const user = await fetchUser();
  const posts = await fetchPosts(); // 等待用户数据获取完成后才开始
  return { user, posts };
}

// 并行执行 – 快 ✅
async function getParallel() {
  const userPromise = fetchUser(); // 立即开始,不等待
  const postsPromise = fetchPosts(); // 立即开始,不等待

  const user = await userPromise;
  const posts = await postsPromise;

  return { user, posts };
}

更简洁的方式是使用 Promise.all():

async function getParallelBetter() {
  const [user, posts] = await Promise.all([
    fetchUser(),
    fetchPosts()
  ]);
  return { user, posts };
}

2. 在循环中使用 async/await

在循环中使用 await 时需要特别注意执行顺序:

// 顺序执行 – 每个操作等待上一个完成
async function processSequentially(items) {
  for (const item of items) {
    await processItem(item);
  }
}

// 并行执行 – 所有操作同时开始
async function processParallel(items) {
  const promises = items.map(item => processItem(item));
  await Promise.all(promises);
}

Promise vs async/await 对比

Promise 写法
function fetchUser() {
  return fetch(‘/api/user’)
    .then(response => {
      if (!response.ok) {
        throw new Error(‘网络响应错误’);
      }
      return response.json();
    })
    .then(user => {
      return fetch(`/api/posts/${user.id}`);
    })
    .then(response => response.json())
    .catch(error => {
      console.error(‘请求失败:’, error);
    });
}
async/await 写法
async function fetchUser() {
  try {
    const response = await fetch(‘/api/user’);
    if (!response.ok) {
      throw new Error(‘网络响应错误’);
    }

    const user = await response.json();
    const postsResponse = await fetch(`/api/posts/${user.id}`);
    const posts = await postsResponse.json();

    return posts;
  } catch (error) {
    console.error(‘请求失败:’, error);
  }
}

async/await 使异步代码更接近同步代码的写法,大大提高了可读性和可维护性。

总结

1. async 函数:声明一个异步函数,它总是返回一个 Promise

2. await 表达式:暂停 async 函数的执行,等待 Promise 的结果

3. 错误处理:使用 try/catch 或 Promise.catch() 处理错误

4. 并行优化:没有依赖关系的操作应该并行执行

5. 兼容性:现代浏览器和Node.js(8+)都支持 async/await

async/await 并不是替代 Promise,而是在 Promise 之上提供了更简洁的语法糖。

掌握了 async/await,你就拥有了编写干净、高效异步JavaScript代码的能力!