前言
从回调地狱到 Promise 链式调用,JavaScript 异步编程一直在进化。async/await 的出现,让我们能用编写同步代码的方式来写异步逻辑,它不仅是语法糖,更是对异步流程控制的一次革命。
一、 async:Promise 的包装器
async 是一个修饰符,用于声明一个异步函数。
-
自动包装:
async函数总是返回一个 Promise。 -
返回值:
- 如果
return的不是 Promise,引擎会自动用Promise.resolve()包装它。 - 如果抛出错误,则返回一个被
reject的 Promise。
- 如果
async function fun() {
// 即使这里只 return 'hello',外部收到的也是 Promise
return "成功操作";
}
fun().then(res => console.log(res)); // "成功操作"
二、 await:暂停执行的艺术
await 只能在 async 函数中使用。它的核心作用是等待。
1. 等待的是什么?
- 如果是 Promise:
await会暂停async函数内部代码的执行,直到 Promise 状态变为fulfilled或rejected。 - 如果是普通值:会立即将其转换为一个已解决(resolved)的 Promise。
2. 执行机制
当代码执行到 await 时:
- 它会立即执行
await后面的表达式。 - 重点:
async函数会暂时“让出”主线程,跳出该函数去执行外部的同步代码。 - 等主线程同步任务清空后,再回来继续执行
async函数中await之后的代码(这部分代码会被当作微任务处理)。
async function foo() {
console.log(1);
let a = await 2; // 这里会暂停,跳出 foo 执行外部同步任务
console.log(a); // 等微任务队列轮到它时,输出 2
console.log(3);
}
console.log(4);
foo();
console.log(5);
// 输出顺序:4 -> 1 -> 5 -> 2 -> 3
三、 实战:优雅地处理异步依赖
当你需要按照特定顺序发送请求,或者 C 请求依赖 A 和 B 的结果时,async/await 比 .then() 链式调用清晰得多。
async function getData() {
try {
const resA = await requestA(); // 等 A 完成
const resB = await requestB(resA); // 带着 A 的结果等 B
return await requestC(resB); // 返回 C 的结果
} catch (error) {
console.log("捕获到错误:", error); // 统一处理异步错误
}
}
四、 深度思考:事件循环挑战赛
提示:执行await foo()时,会先执行foo函数,再将其变为promise对象
async function foo() {
console.log('foo')
}
async function bar() {
console.log('bar start')
await foo()
console.log('bar end')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
bar();
new Promise(function (resolve) {
console.log('promise executor')
resolve();
}).then(function () {
console.log('promise then')
})
console.log('script end')
💡 解析步骤:
-
执行同步任务:输出
script start。 -
执行
setTimeout:将其回调放入宏任务队列。 -
调用
bar():输出bar start。 -
执行
await foo():调用foo(),输出foo。此时bar函数暂停,console.log('bar end')被推入微任务队列。 -
执行 Promise 构造函数:输出
promise executor。其.then()回调被推入微任务队列。 -
执行同步任务:输出
script end。 -
清空微任务队列:
- 取出第一个微任务:输出
bar end。 - 取出第二个微任务:输出
promise then。
- 取出第一个微任务:输出
-
执行宏任务:输出
setTimeout。
最终顺序: script start -> bar start -> foo -> promise executor -> script end -> bar end -> promise then -> setTimeout
五_ 面试模拟题
Q1:async/await 对比 Promise 有什么优缺点?
参考回答:
- 优点:代码更像同步代码,可读性极高;错误处理可以使用
try...catch,逻辑更统一;调试更友好(堆栈跟踪更清晰)。 - 缺点:如果不小心写了多个
await而这些请求本可以并行,会造成性能浪费(此时应配合Promise.all)。
Q2:如果在 async 函数中不写 await,这个函数还是异步的吗?
参考回答: 从返回值来看,它依然会返回一个 Promise,是异步的。但从执行过程看,如果不遇到 await,函数内部的代码会像普通函数一样同步执行完毕,不会中途让出主线程。
Q3:如何并发执行两个不相关的异步请求?
参考回答: 不要连续写两个 await。应该先启动请求,再 await 它们的结果,或者使用 Promise.all。
JavaScript
// ❌ 错误:耗时 A + B
const a = await getA();
const b = await getB();
// ✅ 正确:耗时 Max(A, B)
const [a, b] = await Promise.all([getA(), getB()]);