异步编程进阶:Promise与async/await深度剖析

Promise是状态机,通过then链式调用返回新Promise,async/await以同步语法处理异步,基于Promise并依赖事件循环的微任务队列,合理使用可避免回调地狱并提升代码可读性与健壮性。

JavaScript 是单线程语言,异步编程是其核心能力之一。随着应用复杂度提升,回调地狱(Callback Hell)问题愈发突出,Promise 和 async/await 应运而生,成为现代前端开发的标配。要真正掌握异步编程,不能只停留在“会用”,更要理解其内部机制与最佳实践。

Promise:从状态机到链式调用

Promise 本质是一个状态机,具有三种状态:pending(等待)、fulfilled(成功)、rejected(失败)。一旦状态变更,就不会再改变,这是 Promise 可靠性的基础。

创建一个 Promise 很简单:

const myPromise = new Promise((resolve, reject) => { setTimeout(() => { Math.random() > 0.5 ? resolve("成功") : reject("失败"); }, 1000); });

通过 .then().catch() 注册回调函数,实现链式调用:

myPromise .then(result => { console.log(result); return result + " -> 第二次处理"; }) .then(data => console.log(data)) .catch(error => console.error("出错了:", error));

关键点在于:每个 then 返回一个新的 Promise,从而支持链式操作。如果某个 then 回调中抛出异常或返回被拒绝的 Promise,后续的 then 会被跳过,直到遇到 catch。

async/await:同步写法的异步逻辑

async/await 是基于 Promise 的语法糖,让异步代码看起来像同步代码,极大提升可读性。

使用 async 定义的函数总是返回一个 Promise,await 只能在 async 函数内部使用,用于“暂停”执行,等待 Promise 结果。

async function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); return data; } catch (error) { console.error('请求失败', error); throw error; } }

上面代码等价于使用 .then 的链式调用,但结构更清晰。注意:await 并不会阻塞整个程序,只是暂停当前 async 函数的执行,其他任务仍可继续。

错误处理的差异与最佳实践

Promise 使用 .catch() 捕获链中任意环节的错误;async/await 则依赖 try/catch 结构。

常见陷阱:忘记捕获 await 的异常会导致未处理的 Promise rejection。

推荐做法:在调用 async 函数时也进行错误处理:

fetchData() .then(data => console.log(data)) .catch(err => console.error(err));

或者在顶层 async 函数中使用 try/catch 包裹多个 await 调用,避免重复写 catch。

对于并行请求,不要连续 await:

// 错误:串行执行 const a = await fetchA(); const b = await fetchB(); // 正确:并发执行 const [resA, resB] = await Promise.all([fetchA(), fetchB()]);

深入理解事件循环与微任务队列

Promise 的回调属于 微任务(microtask),优先级高于 setTimeout 等宏任务(macrotask)。

这意味着:即使 resolve 立即发生,其 then 回调也不会立即执行,而是等到当前同步代码结束后,在本轮事件循环末尾执行。

console.log(1); Promise.resolve().then(() => console.log(2)); console.log(3); // 输出:1 3 2

async/await 的 await 后表达式一旦 resolve,其后续代码会被包装成微任务加入队列,这也是为什么 await 后的语句不会立刻执行的原因。

基本上就这些。掌握 Promise 的状态流转、链式机制,理解 async/await 如何封装 Promise,并熟悉微任务调度,才能写出高效、健壮的异步代码。不复杂,但容易忽略细节。