现在位置: 首页 > Javascript 教程 > 正文

JavaScript async/await

在讲解 async/await 之前,我们需要先理解 JavaScript 中的异步编程概念,可以参考:JavaScript 异步编程

JavaScript 是单线程语言,意味着它一次只能执行一个任务。为了避免长时间运行的任务阻塞主线程,JavaScript 使用异步编程模型。

异步 vs 同步

  • 同步(Synchronous)编程:代码按顺序执行,前一个操作完成后才会执行下一个
  • 异步编程(Asynchronous):某些操作被放入"任务队列",主线程继续执行后续代码,等主线程空闲时再处理队列中的任务

实例

// 同步示例
console.log('1');
console.log('2');
// 输出: 1, 2

// 异步示例
console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
// 输出: 1, 3, 2

回调函数的问题

在 async/await 出现之前,JavaScript 主要使用回调函数处理异步操作,但这会导致"回调地狱"(Callback Hell)。

实例

getData(function(a) {
  getMoreData(a, function(b) {
    getMoreData(b, function(c) {
      getMoreData(c, function(d) {
        console.log(d);
      });
    });
  });
});

这种嵌套结构使得代码难以阅读和维护。


Promise 的引入

ES6 引入了 Promise 对象来解决回调地狱问题。

实例

function getData() {
  return new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => resolve('数据'), 1000);
  });
}

getData()
  .then(data => {
    console.log(data);
    return getMoreData(data);
  })
  .then(moreData => {
    console.log(moreData);
  })
  .catch(error => {
    console.error(error);
  });

虽然 Promise 改善了回调问题,但 then() 链式调用仍然不够直观。


async/await 语法

ES2017 引入了 async/await,它建立在 Promise 之上,让异步代码看起来像同步代码一样。

async 函数

在函数声明前添加 async 关键字,表示该函数是异步的:

async function fetchData() {
  // 函数体
}

async 函数总是返回一个 Promise:

  • 如果返回值不是 Promise,会自动包装成 resolved Promise
  • 如果抛出异常,会返回 rejected Promise

await 表达式

await 只能在 async 函数内部使用:

async function fetchData() {
  const result = await somePromise;
  console.log(result);
}

await 会暂停 async 函数的执行,等待 Promise 完成:

  • 如果 Promise 被 resolve,返回 resolve 的值
  • 如果 Promise 被 reject,抛出错误(可以用 try/catch 捕获)

实际应用示例

基本用法

实例

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function showMessage() {
  console.log('开始');
  await delay(1000);
  console.log('1秒后');
  await delay(1000);
  console.log('又1秒后');
}

showMessage();

错误处理

实例

async function fetchUserData() {
  try {
    const response = await fetch('https://api.example.com/user');
    if (!response.ok) {
      throw new Error('网络响应不正常');
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('获取数据失败:', error);
  }
}

并行执行

如果需要并行执行多个异步操作,可以使用 Promise.all:

实例

async function fetchMultipleData() {
  const [userData, productData] = await Promise.all([
    fetch('/api/user'),
    fetch('/api/products')
  ]);
 
  const user = await userData.json();
  const products = await productData.json();
 
  return { user, products };
}

常见问题与最佳实践

1. 不要忘记 await

实例

// 错误示例 - 忘记 await
async function example() {
  const data = fetch('/api'); // 缺少 await
  console.log(data); // 输出 Promise 对象
}

// 正确示例
async function example() {
  const data = await fetch('/api');
  console.log(data); // 输出实际数据
}

2. 避免不必要的 async

实例

// 不必要 - 函数内部没有 await
async function unnecessaryAsync() {
  return 42;
}

// 更简单的写法
function simpleFunction() {
  return 42;
}

3. 顶层 await

在模块顶层可以直接使用 await(ES2022 特性):

实例

// 在模块中
const data = await fetch('/api');
console.log(data);

4. 性能考虑

  • 顺序执行 vs 并行执行:合理使用 Promise.all 提高性能
  • 错误处理:确保所有可能的错误都被捕获

async/await 与传统 Promise 对比

特性 async/await Promise then/catch
可读性 高,类似同步代码 中,链式调用
错误处理 使用 try/catch 使用 .catch()
调试 更容易,有明确的调用栈 较困难,调用栈可能不清晰
代码结构 更扁平 嵌套或链式

总结

async/await 是 JavaScript 异步编程的重大改进,它:

  1. 使异步代码更易读、更易维护
  2. 基于 Promise,与现有 Promise 代码兼容
  3. 提供了更直观的错误处理方式
  4. 改善了调试体验

虽然 async/await 不是完全替代 Promise,但在大多数情况下,它提供了更优雅的解决方案。理解 async/await 的工作原理和最佳实践,将大大提升你的 JavaScript 异步编程能力。