问题描述
很多人说Async/await是基于Promise + generator之上的语法糖。请问它的底层实现真的是Promise吗?还是基于不同的东西?
这两天刚好在某个JavaScript引擎中实现并测试好了async/await语法,底层实现肯定是围绕着Promise实现的,但一般来说并不是也没有必要在(C语言或C++语言层面)底层,搞一套模拟generator行为来实现async/await语法,底层有更加简单直接的方法。
经过测试async/await语法是可以正常使用的:

async函数底层实现:async函数行为很简单,就是看函数的返回值是否为Promise,如果不是Promise,则用resolve转成Promise。
await语法会去看后面的是否为Promise,不是Promise,就当做没await语句,啥也不做。如果是Promise,要看这几种情况:
1、fulfilled状态的Promise,此时直接返回Promise中的PromiseResult对象的值。
2、rejected状态的Promise,此时可以报错或者抛异常,看引擎实现。这里的JavaScript是直接报错终止程序执行。
3、pending转态的Promise,此时await就忙了,首先拷贝此时的调用栈和程序计数器PC,然后将此函数加入事件循环(并注册一个事件循环回调函数,底层),最后直接返回函数(此时返回的必然是一个pending状态的Promise)。
明确这几点就可以写C语言底层代码了。
async的底层实现很简单,直接将栈上的函数返回值检查并转换就行了:

async函数执行了to_promise(字节码)指令,此指令调用了下面的转Promise底层(C语言)函数:

await语法就比较麻烦了,await语法会编译成一个await(字节码)指令(当然不同的JavaScript实现会不一样)。

虚拟机执行await指令时会执行下面代码:

其实比较关键的是await_condition这个事件回调函数:

事件循环引擎会不断地调用这个await_condition,这个事件循环其实很简单,就是不断检查和await指令绑定的Promise状态,如果是pending则啥也不做,等就行。是rejected则报错。如果是fulfilled状态,则去调用另一个关键函数:invoke_function_await:

这个函数也很简单,首先恢复await指令之前保存函数调用栈,初始化一些函数调用必要栈和代码(字节码)信息,最后从await之前保存的PC(程序计数器)处开始执行就可了。执行完了,直接移除事件,并释放内存(主要就是函数的调用栈)资源即可。
如果后面继续有await指令则执行相同的步骤:保存函数调用栈和PC->加入事件循环->函数立即返回,事件循环来异步执行await。
我看到有些资料说事件循环会有另外的线程来处理,其他JavaScript引擎实现可能会有多个线程,但这个JavaScript引擎自始至终都只有一个线程在执行代码(即:主线程执行完同步代码后直接进入事件循环),包括后面的事件循环都是一个线程再跑,始终没有开辟第二个线程,所以异步调用和事件循环并不需要其他线程来跑,也是和多线程没有关系的。