返回首页

14. 说说你对async/await的理解?

问题解析

async/await是ES2017引入的异步编程语法糖,它基于Promise实现,让异步代码看起来像同步代码一样。理解async/await的工作原理、执行顺序以及与Promise的关系是面试中的重要考点。

核心概念

async函数

async关键字用于声明一个异步函数,该函数会隐式返回一个Promise对象。

await关键字

await关键字只能在async函数内部使用,用于等待一个Promise对象的结果。它会暂停async函数的执行,等待Promise完成,然后恢复执行并返回结果。

详细解答

async函数的基本用法

async function asyncFunction() {
  return 'Hello';
}

// 等价于
function asyncFunction() {
  return Promise.resolve('Hello');
}

asyncFunction().then(value => console.log(value)); // 'Hello'

await的基本用法

async function getData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('获取数据失败:', error);
  }
}

async/await的执行流程

async function fn1() {
  console.log(1);
  await fn2();
  console.log(2); // 被await阻塞,加入微任务队列
}

async function fn2() {
  console.log('fn2');
}

fn1();
console.log(3);

// 输出顺序: 1 -> fn2 -> 3 -> 2

执行过程分析

  1. 执行fn1,打印1
  2. 遇到await fn2(),先执行fn2,打印fn2
  3. await阻塞后面的代码,将其加入微任务队列
  4. 跳出async函数,执行同步代码,打印3
  5. 同步代码执行完毕,执行微任务,打印2

深入理解

await会阻塞什么

await会阻塞async函数内部的后续代码,但不会阻塞主线程。它会将await后面的代码放入微任务队列,等待当前调用栈清空后再执行。

async function test() {
  console.log('开始');
  await Promise.resolve();
  console.log('中间'); // 进入微任务队列
  await Promise.resolve();
  console.log('结束'); // 进入微任务队列
}

test();
console.log('外部');

// 输出: 开始 -> 外部 -> 中间 -> 结束

错误处理

async function fetchData() {
  try {
    const response = await fetch('/api/data');
    if (!response.ok) {
      throw new Error('网络请求失败');
    }
    return await response.json();
  } catch (error) {
    console.error('错误:', error);
    // 可以选择重新抛出或返回默认值
    throw error;
  }
}

// 或者使用.catch
fetchData().catch(error => console.error(error));

并行执行

// 错误:串行执行,效率低
async function getDataSlow() {
  const users = await fetchUsers();  // 等待2秒
  const posts = await fetchPosts();  // 再等待2秒
  return { users, posts }; // 总共4秒
}

// 正确:并行执行
async function getDataFast() {
  const [users, posts] = await Promise.all([
    fetchUsers(),
    fetchPosts()
  ]);
  return { users, posts }; // 总共2秒
}

最佳实践

  1. 使用try-catch处理错误:async/await让错误处理更像同步代码
  2. 避免不必要的await:独立的异步操作应该并行执行
  3. 注意循环中的await:在循环中使用await会导致串行执行
// 错误:串行执行
async function processItems(items) {
  for (const item of items) {
    await processItem(item); // 一个一个处理
  }
}

// 正确:并行执行
async function processItems(items) {
  await Promise.all(items.map(item => processItem(item)));
}

// 如果需要控制并发数
async function processItemsWithLimit(items, limit = 5) {
  const results = [];
  for (let i = 0; i < items.length; i += limit) {
    const batch = items.slice(i, i + limit);
    const batchResults = await Promise.all(
      batch.map(item => processItem(item))
    );
    results.push(...batchResults);
  }
  return results;
}

面试要点

  1. async函数返回值:总是返回Promise对象
  2. await的阻塞特性:阻塞async函数内部代码,但不阻塞主线程
  3. 执行顺序:理解同步代码、微任务、宏任务的执行顺序
  4. 错误处理:掌握try-catch和.catch的使用
  5. 性能优化:避免不必要的串行await,使用Promise.all并行执行

经典面试题

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2');
}

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

async1();

new Promise(function(resolve) {
  console.log('promise1');
  resolve();
}).then(function() {
  console.log('promise2');
});

console.log('script end');

输出顺序

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

解析

  1. script start(同步代码)
  2. async1 start(同步代码)
  3. async2(await先执行右侧函数)
  4. promise1(Promise构造函数同步执行)
  5. script end(同步代码)
  6. async1 end(await后的代码进入微任务)
  7. promise2(then回调微任务)
  8. setTimeout(宏任务)