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
执行过程分析:
- 执行fn1,打印1
- 遇到await fn2(),先执行fn2,打印fn2
- await阻塞后面的代码,将其加入微任务队列
- 跳出async函数,执行同步代码,打印3
- 同步代码执行完毕,执行微任务,打印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秒
}
最佳实践
- 使用try-catch处理错误:async/await让错误处理更像同步代码
- 避免不必要的await:独立的异步操作应该并行执行
- 注意循环中的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;
}
面试要点
- async函数返回值:总是返回Promise对象
- await的阻塞特性:阻塞async函数内部代码,但不阻塞主线程
- 执行顺序:理解同步代码、微任务、宏任务的执行顺序
- 错误处理:掌握try-catch和.catch的使用
- 性能优化:避免不必要的串行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
解析:
- script start(同步代码)
- async1 start(同步代码)
- async2(await先执行右侧函数)
- promise1(Promise构造函数同步执行)
- script end(同步代码)
- async1 end(await后的代码进入微任务)
- promise2(then回调微任务)
- setTimeout(宏任务)