深入剖析await/await:状态机/Generator原理
JavaScript的async/await本质是Generator+Promise+自动执行器的组合实现。本文章展示了如何通过递归调用.next()和Promise.then()实现自动流程控制,最终形成"写起来像同步、执行是异步"的编程体验。
1.概述
JavaScript的async/await尝尝被描述为“写起来像同步、执行是异步”,这种体验的背后是通过Generator+Promise+自动调度器(状态机)来进行实现的。
2.深入理解
2.1 async/await本质是什么?
在JavaScript中:
- async function会被编译为一个Generator函数
- 每一个await会被编译为yield一个Promise(Generator及其 yield 本质上是完全同步的,异步行为来源于Promise)
- 由一个隐藏的执行器(runtime或babel)不断调用.next()来驱动Generator继续执行
async/await 的语法糖 = Generator 的语法增强 + Promise 异步能力 + 自动状态机调度
示例:
async function foo() {
const a = await delay(1000, 'a')
const b = await delay(1000, 'b')
return a + b
}
相当于:
function* foo() {
const a = yield delay(1000, 'a')
const b = yield delay(1000, 'b')
return a + b
}
所以现在问题就在,JavaScript如何定义一个执行器来不断.next()恢复这个Generator(next的作用是执行从当前到下一个yield处并返回{ value: Promise/其他, done: boolean })
2.2 从Generator角度理解async/await
一个Generator函数执行后不会立刻运行,而是返回一个“暂停”的迭代器
const gen = foo();
gen.next(); // 开始执行
gen.next(v); // 将上一个 yield 的结果传回去
gen.next(v); // 返回完整结果
如果一个 Generator 中有N个yield,就会产生N+1个执行阶段:
start → yield1 → yield2 → return (done)
这也就是为什么,有两个await,会触发三次next()。在这个过程中,yield了一个Promise的时候,执行器看到yield了一个 Promise,然后等待Promise.resolve,然后再调用generator.next(resolvedValue),此时这个等待过程才是异步的。
2.3 Promie与await的关系
const a = await delay(...);
编译为:
const a = yield delay(...);
说明以下几点:
- await后必须跟一个Promise
- yield的值必须让执行器等待Promise resolve
- resolve的结果作为下一次next(value)的形参传回Generator
3.手写async/await执行器
利用Generator+Promise,将yield Promise的逻辑自动化
<script>
function run(generationFn) {
// 获取 generator 对象(初始暂停态,未开始执行函数体)
const gen = generationFn()
function step(nextFn, value) {
let next
try {
console.log('value=', value);
// 推进下一步,实际上执行的是传进来的 gen.next
// 遇到 yield 表达式,返回一个 { value: Promise /* delay(...) */, done: boolean } 对象
// 函数体里面有 n ci yield,那么就 next n+1 次,函数开始到第一个 yield/yield 之间的切换/最后 return
/*
比如执行完第一轮,获取到 a 的 Promise,然后在 Promise.resolve 那里解析完拿到结果'a',在第二轮这里传进来
第二轮传进来的时候执行到 nextFn,此时做了2件件事:1.将'a'赋值给a 2.执行下一行代码直到下一个 yield
*/
next = nextFn(value)
console.log('next=', next);
} catch (e) {
return Promise.reject(e)
}
// 若 next.done 为true,则说明执行完成
if (next.done) {
return Promise.resolve(next.value)
}
// 等待异步函数的结果,再继续执行(next.value 为 Promise)
return Promise.resolve(next.value)
.then(
v => step(gen.next.bind(gen), v),
e => step(gen.throw.bind(gen), e))
}
// 开始执行,逐步推进整个状态机(第一次未传入,value 为 undefined)
return step(gen.next.bind(gen))
}
// 模拟异步状态,返回 Promise 对象
function delay(ms, value) {
return new Promise(resolve => {
setTimeout(() => resolve(value), ms);
});
}
// start -- next() --> yield #1 -- next(v) --> yield #2 -- next(v) --> return
function testAsync() {
return run(function* () {
const a = yield delay(1000, 'a')
const b = yield delay(1000, 'b')
return a + b
})
}
testAsync().then(console.log)
</script>
第一次进入next,value为undefined:
![]()
运行结果:

核心逻辑:
- 遇到yield自动暂停
- 等Promise.resolve完成后继续next
- 将resolve的值作为next(value)传入Generator
- 循环完成,直到return
4.状态机视角
无论是V8引擎(支持async/await)还是Babel(把async/await 转成generator+run函数),最终都会把async函数转换成一个显式的状态机,结构大概如下:
state 0: 执行到第一个 await(yield)
state 1: 从 await 恢复,继续执行到第二个 await
state 2: 从 await 恢复,执行 return
await并非是阻塞(不阻塞全局线程,而是暂停async函数的上下文,await后续的函数体逻辑会被拆分为微任务,在Promise.resolve后执行),只是让当前函数“暂停”并等待Promise结果,由执行器保证暂停后的恢复顺序,这也就是为什么JS单线程模型能同时保证异步能力与同步书写的基础
更多推荐


所有评论(0)