面试题答案
一键面试使用async/await实现复杂异步场景
假设我们有三个异步操作 asyncOperation1
、asyncOperation2
和 asyncOperation3
,其中 asyncOperation1
和 asyncOperation2
并发执行,然后 asyncOperation3
在它们完成后顺序执行。
function asyncOperation1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Operation 1 completed');
}, 1000);
});
}
function asyncOperation2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Operation 2 completed');
}, 1500);
});
}
function asyncOperation3() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Operation 3 completed');
}, 1200);
});
}
async function main() {
try {
const [result1, result2] = await Promise.all([asyncOperation1(), asyncOperation2()]);
console.log(result1);
console.log(result2);
const result3 = await asyncOperation3();
console.log(result3);
} catch (error) {
console.error('An error occurred:', error);
}
}
main();
通过try - catch块处理异常
在上述代码中,try - catch
块包裹了所有的异步操作。Promise.all
用于并发执行 asyncOperation1
和 asyncOperation2
。如果任何一个Promise被拒绝,Promise.all
会立即拒绝,并抛出错误,这个错误会被 catch
块捕获。await
操作符后的Promise如果被拒绝,同样会抛出错误,也会被 catch
块捕获。这样就可以在一个地方统一处理所有异步操作中可能出现的错误。
async/await相较于Promise的优势
- 代码更简洁易读:
async/await
基于Promise
构建,但语法上更接近同步代码,使得异步代码看起来和同步代码类似,大大提高了代码的可读性。例如上述代码,await
使得我们可以按顺序书写异步操作,而不需要像链式调用Promise
那样层层嵌套。 - 错误处理更直观:通过
try - catch
块可以统一捕获所有异步操作中的错误,而在Promise
链式调用中,需要在每个catch
块分别处理错误,或者在最后统一处理,相对复杂。
async/await潜在问题
- 阻塞问题:
await
会阻塞其所在函数中后续代码的执行,直到Promise被解决。如果在一个循环中使用await
,可能会导致性能问题,因为每次迭代都要等待前一个异步操作完成。在这种情况下,可能需要结合Promise.all
等方法并发执行部分异步操作。 - 难以调试:由于
async/await
语法糖的存在,错误堆栈可能不够清晰。当发生错误时,调试工具可能无法直接定位到实际出错的异步操作位置,需要花费更多精力去排查。