【js引擎】 quickjs 如何处理 async 函数

async 函数是 javascript 中处理异步编程的重要关键字。如我们所知, async 函数执行时会在遇到第一个await关键字时立即返回一个 promise。本文将介绍 quickjs 是如何处理这样一个流程的。

测试样例代码如下

async function func() {
    console.log("1");
    let ret = await new Promise((resolve)=>{
        console.log("4");
        resolve(10);
        console.log("3");
    });
    return ret;
}


func().then((ret)=> {
    console.log(ret);
});

console.log("2");

将其编译成 quickjs 字节码后如下

input.js:3: function: <null>
  args: resolve
  stack_size: 3
  opcodes:
;; (resolve)=>{
;;         console.log("4");

        get_var console
        get_field2 log
        push_const8 0: 1"4"
        call_method 1
        drop

;;         resolve(10);

        get_arg0 0: resolve
        push_i8 10
        call1 1
        drop

;;         console.log("3");

        get_var console
        get_field2 log
        push_const8 1: 1"3"
        call_method 1

;;     }

        return_undef

/* async func 编译的字节码 */
input.js:1: function: func
  locals:
    0: let ret [level:1 next:-1]
  stack_size: 3
  opcodes:
;; async function func() {

        set_loc_uninitialized 0: ret

;;     console.log("1");

        get_var console
        get_field2 log
        push_const8 0: 1"1"
        call_method 1
        drop

;;     let ret = await new Promise((resolve)=>{

        get_var Promise
        dup

;;         console.log("4");
;;         resolve(10);
;;         console.log("3");
;;     });

        fclosure8 1: [bytecode <null>]
        call_constructor 1
        await
        put_loc0 0: ret

;;     return ret;

        get_loc_check 0: ret
        return_async

;; }

input.js:12: function: <null>
  args: ret
  stack_size: 3
  opcodes:
;; (ret)=> {
;;     console.log(ret);

        get_var console
        get_field2 log
        get_arg0 0: ret
        call_method 1

;; }

        return_undef

/*整个模块的字节码*/
input.js:1: function: <eval>
  locals:
    0: var <ret>
  stack_size: 3
  opcodes:
        check_define_var func,64
        fclosure8 0: [bytecode func]
        define_func func,0
        get_var func
        call0 0
        get_field2 then
        fclosure8 1: [bytecode <null>]
        call_method 1
        put_loc0 0: "<ret>"
        get_var console
        get_field2 log
        push_const8 2: 1"2"
        call_method 1
        set_loc0 0: "<ret>"
        return

我们知道,quickjs 中执行 js 函数的代码是 JS_CallInternal
根据以上字节码信息,JS_CallInternal 第一次调用,会进入执行整个模块的代码,执行到

call0 0

时,会进入执行

async func() { ... } 的代码

挂 gdb 在 JS_CallInternal 打断点试一下
这是第一个 JS_CallInternal 执行 call0 字节码的地方
在这里插入图片描述
我们 s 进去,看看怎么执行 async 函数的

在判断函数类型的时候,发现不是一个 字节码函数
在这里插入图片描述
打印 p->class_id 发现它是 52,对应到

eunm {
	...
	JS_CLASS_ASYNC_FUNCTION,
	...
}

非常合理
然后进入16271 行的 c 函数执行

这个call_func 实际上是:

static JSValue js_async_function_call(JSContext *ctx, JSValueConst func_obj,
                                      JSValueConst this_obj,
                                      int argc, JSValueConst *argv, int flags)
{
    JSValue promise;
    JSAsyncFunctionState *s;

    s = async_func_init(ctx, func_obj, this_obj, argc, argv);
    if (!s)
        return JS_EXCEPTION;

    promise = JS_NewPromiseCapability(ctx, s->resolving_funcs);
    if (JS_IsException(promise)) {
        async_func_free(ctx->rt, s);
        return JS_EXCEPTION;
    }

    js_async_function_resume(ctx, s);
    
    async_func_free(ctx->rt, s);

    return promise;
}

代码并不长,重要逻辑是

  1. 创建了 Promise,用来做 resolve 之后回复 async 函数的执行
  2. js_async_function_resume 是真正开始执行 async 函数。虽然叫 resume(恢复),但是第一次执行和后续的恢复执行逻辑是一样的,所以就用这个了。

根据上面的字节码信息,await 关键字 被翻译成了 OP_await 字节码
他的操作很简答,如下
在这里插入图片描述
在这里插入图片描述
记录下当前栈帧位置然后直接退出函数执行。
后续的操作就由 Promise 来接管了。
本来想到这里就结束的。结果发现接下来的操作更有有趣,就继续写一下。promise 是如何接管接下来的异步操作的?
我们先建立如下模型,理解其中的两个 promise

async func() {
	code1
	await new promise;  // promise1
	code2
}
let promise2 = func();

调用 func 之后,我们期待的行为是这样的

  1. 执行 code1
  2. 执行 promise1,然后退出 func
  3. promise1,resolve 后 执行code2。相当于恢复 func 的执行。

quickjs 的做法是,退出 func 时保存 func 的执行状态。利用一个 promise2 和 promise1 建立联系。

promise2 的 链式调用(相当于使用 then 注册的回调) 就是执行

js_async_function_resume

也就是上面说的恢复执行函数。
而 promise1 的 then,其实就是将 promise2 的状态改变为 resolve 态。

所以 quickjs 在执行完一次 func 后,还需要将内部的 promise1 拿出来,和自己保存的 promise2 建立一个联系
在这里插入图片描述
代码在这一行,关键代码注释如下:

	value = s->frame.cur_sp[-1]; // value 是 await 的对象,一般是promise1
	...
	// 下面这一句处理 value 不是一个 promsie 的情况。毕竟 await 3; 这样的代码也是正确的
    promise = js_promise_resolve(ctx, ctx->promise_ctor,
                                1, (JSValueConst *)&value, 0);
    // 创建了 promise2 的 resolve 函数,其类型是:JS_CLASS_ASYNC_FUNCTION_RESOLVE
    if (js_async_function_resolve_create(ctx, s, resolving_funcs)) {
        JS_FreeValue(ctx, promise);
        goto fail;
    }

	...
    // 下面这一句建立 promise1 和 promise2 之间的联系,
    // 也就是吧 resolving_funcs 放入 promise1 的链式调用队列中。
    res = perform_promise_then(ctx, promise,
                                (JSValueConst *)resolving_funcs,
                                (JSValueConst *)resolving_funcs1);
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值