JS-异步编程async/await 语法糖

37 阅读3分钟

前言

从回调地狱到 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. 等待的是什么?

  • 如果是 Promiseawait 会暂停 async 函数内部代码的执行,直到 Promise 状态变为 fulfilledrejected
  • 如果是普通值:会立即将其转换为一个已解决(resolved)的 Promise。

2. 执行机制

当代码执行到 await 时:

  1. 它会立即执行 await 后面的表达式。
  2. 重点async 函数会暂时“让出”主线程,跳出该函数去执行外部的同步代码。
  3. 等主线程同步任务清空后,再回来继续执行 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')

💡 解析步骤:

  1. 执行同步任务:输出 script start

  2. 执行 setTimeout:将其回调放入宏任务队列。

  3. 调用 bar():输出 bar start

  4. 执行 await foo():调用 foo(),输出 foo。此时 bar 函数暂停,console.log('bar end') 被推入微任务队列

  5. 执行 Promise 构造函数:输出 promise executor。其 .then() 回调被推入微任务队列

  6. 执行同步任务:输出 script end

  7. 清空微任务队列

    • 取出第一个微任务:输出 bar end
    • 取出第二个微任务:输出 promise then
  8. 执行宏任务:输出 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()]);