如果你是一名前端开发者,一定对 async/await 不陌生。它让我们用同步的方式写异步代码,堪称 JavaScript 语法糖中的“顶流”。但你真的理解它的底层原理吗?近日,一篇技术博文《我读了一遍 Babel 编译后的 async/await,终于搞懂了它的原理(附 20 行手写实现)》在开发者社区引发热议,作者通过深入解读 Babel 编译生成的代码,将“黑盒”彻底打开。
async/await 的“背后大佬”是 Generator
很多开发者知道 async/await 是基于 Promise 的语法糖,但很少有人深究它和 Generator(生成器函数)的关系。文章指出,Babel 在编译 async/await 时,核心思路就是将其转化为一个状态机,而这个状态机的底层运行逻辑,与 Generator 的“暂停-恢复”机制如出一辙。
简单说,Generator 函数可以通过 yield 关键字暂停执行,并通过 .next() 恢复执行。而 async/await 中的 await 暂停执行逻辑,等待 Promise 完成后再继续运行——本质上就是在“模拟”Generator 的行为。Babel 的编译工作,就是把这个“模拟”的过程变成了实实在在的代码。
Babel 编译后的代码长什么样?
文章中展示了一段常见的 async 函数:
async function fetchData() {
const a = await fetch('url1');
const b = await fetch('url2');
return a + b;
}
Babel 编译后会生成一个包含 _asyncToGenerator 辅助函数的代码块。核心逻辑是:
- 将 async 函数体包裹在一个返回 Promise 的 Generator 函数中。
- 通过
_asyncToGenerator辅助函数,驱动 Generator 逐步执行,并在每个yield(即 await 的位置)等待 Promise 决议。 - 当所有 Promise 都完成,最终返回结果。
作者强调,读懂这段编译后的代码,关键在于理解“状态机如何控制执行流程”以及“Promise 是如何被串联起来的”。
20 行代码手写实现核心原理
为了让读者彻底掌握原理,文章还提供了一个仅 20 行的手写实现。作者去掉了 Babel 编译中大量的边界处理代码,只保留了最核心的逻辑:一个 asyncToGenerator 函数,接收一个 Generator 函数作为参数,返回一个 Promise。
核心步骤只有三步:
- 调用 Generator 函数生成迭代器。
- 定义一个
next函数,从迭代器中不断取值(value 通常是 Promise)。 - 通过
.then链式调用,将上一个 Promise 的结果传给下一个yield,直到迭代器结束。
作者指出:“当你亲手写出这 20 行代码,再回头看 Babel 的编译产物,你会发现一切都清晰了。async/await 不是魔法,它只是一套精心设计的流程控制。”
对前端开发者意味着什么?
理解 async/await 的编译原理,不仅能帮助你更自信地使用它,还能让你在遇到异步错误时快速定位问题。比如,当 await 后面的 Promise 被 reject,错误是如何一层层向上冒泡的?这条链路在编译后的代码中一目了然。
更重要的是,这种“读懂编译产物”的学习方法,可以迁移到其他语法特性上——比如解构赋值、箭头函数、class 语法等。Babel 就像一面“照妖镜”,把 ES6+ 的优雅语法打回原形,而读懂这些“原形”,才是真正的进阶之路。
正如文章作者在结尾所说:“不要只做语言的使用者,试着做一次语言的解释者。当你亲手写出那 20 行代码时,你离‘精通’又近了一步。”
目前,这篇技术文章已在多个开发者社区获得超过 3000 次阅读,不少读者评论:“终于有人把这件事讲明白了。” 如果你也对 async/await 的底层充满好奇,不妨也打开 Babel 编译后的代码,亲自读一遍。