async/await
是 JavaScript 中处理异步操作的核心语法糖,其底层结合了 生成器(Generator) 、Promise 和 事件循环(Event Loop) 的机制,实现了异步代码的同步化书写。以下从核心原理、执行流程和底层机制三个层面展开解析:
一、核心原理:语法糖背后的 Promise 与协程
1. async 函数的本质
-
返回 Promise :
async
函数始终返回一个 Promise 对象。即使函数内部返回非 Promise 值,也会被自动包装为Promise.resolve(value)
。javascript
javascriptasync function foo() { return 1 } console.log(foo()); // Promise { <resolved>: 1 }
-
异常处理 :函数内部抛出的错误会被包装为
Promise.reject(error)
,可通过try/catch
或.catch()
捕获。
2. await 的暂停与恢复机制
-
非阻塞暂停 :
await
会暂停当前async
函数的执行,但不阻塞主线程。后续代码会被封装为微任务,等待右侧 Promise 解决后恢复执行。javascript
javascriptasync function bar() { console.log(1); await Promise.resolve(); // 暂停,后续代码进入微任务队列 console.log(2); } bar(); console.log(3); // 输出顺序:1 → 3 → 2
-
自动 Promise 化 :若
await
后接非 Promise 值,会自动包装为Promise.resolve(value)
。javascript
csharpconst result = await 42; // 等价于 await Promise.resolve(42)
二、执行流程:事件循环与微任务的协作
1. 事件循环的调度逻辑
-
宏任务(MacroTask)与微任务(MicroTask) :
- 宏任务 :包括
setTimeout
、setInterval
、I/O 操作、UI 渲染等。 - 微任务 :包括
Promise.then()
、MutationObserver
、await
后的代码。
- 宏任务 :包括
-
执行顺序:
- 执行当前宏任务中的同步代码。
- 清空微任务队列(按注册顺序执行)。
- 执行下一个宏任务,循环往复。
2. await 的微任务化过程
-
当
await
右侧的 Promise 进入fulfilled
状态时,await
会将后续代码封装为微任务,并通过事件循环调度执行。javascript
javascriptasync function example() { console.log('A'); await new Promise(resolve => setTimeout(resolve, 0)); console.log('B'); } example(); console.log('C'); // 输出顺序:A → C → B(因为 B 是微任务,在当前宏任务结束后执行)
3. 错误处理的集中化
-
try/catch
可捕获await
后的 Promise 拒绝状态,避免分散的.catch()
调用。javascript
javascriptasync function fetchData() { try { const response = await fetch('invalid-url'); const data = await response.json(); } catch (error) { console.error('Error:', error); } }
三、底层机制:生成器与状态机的结合
1. 生成器的暂停与恢复
-
协程模型 :生成器函数通过
yield
关键字实现暂停和恢复,async/await
利用这一特性将异步操作分段执行。javascript
javascriptfunction* generator() { const a = yield Promise.resolve(1); const b = yield Promise.resolve(2); return a + b; }
2. 执行器的自动化驱动
-
co 库的启示 :早期需手动编写执行器驱动生成器,而
async/await
由引擎自动实现。执行器的核心逻辑是递归处理yield
的 Promise,并通过.then()
恢复生成器。javascript
scssfunction co(gen) { return new Promise((resolve, reject) => { const g = gen(); function next(value) { try { const { done, value: res } = g.next(value); if (done) resolve(res); else Promise.resolve(res).then(next, reject); } catch (err) { reject(err); } } next(); }); }
3. V8 引擎的优化
- 状态机实现 :
async
函数被编译为状态机,通过SwitchOnGeneratorState
指令管理执行位置。await
会生成AsyncFunctionAwaitUncaught
内部方法,处理 Promise 的解决与恢复。 - 微任务队列优化 :V8 在处理
await
时直接复用 Promise 实例,避免创建临时 Promise,提升性能。
四、常见误区与最佳实践
1. 误区澄清
- await 不阻塞主线程 :
await
仅暂停当前async
函数,其他任务(如定时器、UI 渲染)仍可执行。 - 顺序执行的本质 :多个
await
的顺序执行依赖事件循环的调度,实际是异步操作的序列化。
2. 最佳实践
-
并发处理 :使用
Promise.all()
并行执行独立的异步操作。javascript
scssconst [res1, res2] = await Promise.all([fetch(url1), fetch(url2)]);
-
避免 forEach 中的 await :
forEach
无法感知异步,应使用for...of
循环。javascript
scssfor (const item of items) { await process(item); // 正确顺序执行 }
五、总结
async/await
通过 生成器的协程机制 实现代码的分段执行,利用 Promise 的状态管理 封装异步操作,最终借助 事件循环的微任务调度 实现非阻塞的 "同步" 执行。其核心优势在于:
-
代码可读性:异步逻辑更接近自然语言。
-
错误处理 :集中式的
try/catch
替代分散的.catch()
。 -
性能优化:与 Promise 性能接近,避免回调地狱的额外开销。
理解这一机制后,开发者可更高效地编写异步代码,同时规避常见的执行顺序陷阱。
文章来源豆包!