【面试题】对async await 的了解?_async awiat 如何实现同步的 面试(1)

最后

基础知识是前端一面必问的,如果你在基础知识这一块翻车了,就算你框架玩的再6,webpack、git、node学习的再好也无济于事,因为对方就不会再给你展示的机会,千万不要因为基础错过了自己心怡的公司。前端的基础知识杂且多,并不是理解就ok了,有些是真的要去记。当然了我们是牛x的前端工程师,每天像背英语单词一样去背知识点就没必要了,只要平时工作中多注意总结,面试前端刷下题目就可以了。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

  1. 首先为register寄存器设置正确的作用域,因为这关系到await point在LoadAccumulatorWithRegister时能否从正确的register里获取结果值,即在JavaScript层面await能否获取到预期结果值

  2. 确定await关键字的FunctionId,这个FunctionId直接决定了await在cxx层面的逻辑处理,即怎么处理跟随的“数据”。上面bytecode里调用的InvokeIntrinsic [_AsyncFunctionAwaitUncaught]正是FunctionId所指向的cxx内部AsyncFunctionBuiltinsAssembler的built-in方法AsyncFunctionAwaitUncaught

 0xefd0025aa16 @   24 : 68 01 f7 02       InvokeIntrinsic [_AsyncFunctionAwaitUncaught], r3-r4
复制代码

  1. 绑定FunctionId作为await的逻辑处理函数,然后获取register列表,并作为闭包参数传给await使用

接着BuildSuspendPoint

为await point标记suspend_id构建suspend point,使得await point可被挂起。suspend_id是suspend point的唯一索引,缓存在generator_jump_table_中。等await触发时,在suspend_id位置挂起async_function_object;await处理完成时从suspend_id对应await point恢复继续执行。

0xefd0025aa1a@28 :affafa0300SuspendGeneratorr0,r0-r2, [0]
 0xefd0025aa1f@33 :b0fafa03ResumeGeneratorr0,r0-r2复制代码
  1. 最后判断当前async_function_object是否需要resume,resume_mode为true,表明async function需要从suspend_id位置ResumeGenerator恢复继续执行,则将async_function_object的当前value返回给当前await point,进入InvokeIntrinsic [_AsyncFunctionResolve]](https://bbs.csdn.net/topics/618166371);否则ReThrow抛错退出进入InvokeIntrinsic [_AsyncFunctionReject]](https://bbs.csdn.net/topics/618166371)
0xefd0025aa1f@33 :b0fafa03ResumeGeneratorr0,r0-r20xefd0025aa23@37 :c1Star30xefd0025aa24@38 :680bfa01InvokeIntrinsic [_GeneratorGetResumeMode],r0-r00xefd0025aa28@42 :c0Star40xefd0025aa29@43 :0cLdaZero0xefd0025aa2a@44 :1cf6TestReferenceEqualr40xefd0025aa2c@46 :9805JumpIfTrue [5] (0xefd0025aa31@51)复制代码

以上bytecode非常清晰展示了async function整个执行过程。

再以直观的示意图来加深印象:

注意:

V8 v7.2之前,无论v值类型都会为其创建临时promise实例

由图中分析:

  1. 创建implicit_promise作为async function返回值,同时createPromise创建临时promise实例用于对v值(命名为promise_42)进行resolvePromise

  2. 针对每一个await point,首先通过performPromiseThen将async function的resume/throw注册到promise_42。然后suspend挂起,等待promise_42处理。一旦fulfilled执行resume并将数据返回await point,进入下一个await point;而rejected就执行throw退出async function。

  3. 最后async function并将implicit_promise返回给调用者,同时归还执行权,调用者继续执行后续script代码,如对implicit_promise进行then方法调用等。

  4. foo函数等待ResolvePromise(promise, promise_42),此时由于ResolvePromise入参promise_42是一个promise实例,会创建

PromiseResolveThenableJob

放入microtask队列等待,而PromiseResolveThenableJob又会创建PromiseReactionJob

V8 v7.2之后,根据v值类型判断是否创建临时Promise实例

图左红底代码块逻辑事实上等价于PromiseResolve

如图右绿底代码,利用PromiseResolvebuilt-in实现,当v值是promise实例时直接复用,即不再创建临时promise实例,这样避免ResolvePromise入参为promise实例而额外创建PromiseResolveThenableJob,而是直接创建PromiseReactionJob。

在最后一个await point接收到从async function resume的返回值42时,由所有await point构建的promise chain进入最后一跳,也就是implicit_promise。async function成功退出,对应implicit_promise进入fulfilled;而如果因任一await point抛错导致的失败退出,对应implicit_promise就进入了rejected,调用者通过then注册的对应状态handler也会执行。

await如何执行

上面分析了await关键字的实现原理,接下来要分析一下await关键字如何执行,也就是cxx层面的built-in方法AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait

首先着重看看注释部分,表达的信息很重要:

  • AsyncFunctionAwait是await关键字的核心逻辑,V8将await value语法糖反解成yield AsyncFunctionAwait(.generator_object, value),表明async function在cxx层面确实是以Generator为基础而封装实现的

OK,接着看一下AsyncFunctionAwait的具体处理逻辑:

  • 首先Return(outer_promise)表明AsyncFunctionAwait返回promise实例,就像foo函数例子里的implicit_promise一样。本质上,从下面这段代码可以发现outer_promise是贯穿于async function的整个执行过程的,简而言之,每一个await point都与这个outer_promise关联。
TNode<JSPromise> outer_promise = LoadObjectField<JSPromise>(
       async_function_object, JSAsyncFunctionObject::kPromiseOffset)

  • 然后重点要提到AsyncFunctionAwaitResolveSharedFunConstant和AsyncFunctionAwaitRejectSharedFunConstant,由它们生成Promise层面的resolve/reject方法对

在所有准备工作完成之后,await就进入真正的处理逻辑AsyncBuiltinsAssembler::Await

首先针对await接收的value值进行PromiseResolve处理,最后得到一个wrapper promise实例。

  • 接着初始化await的上下文,这里的上下文不是async function对应的JSAsyncFunction实例async_function_object,可以理解是接下来马上要创建的onFulfilled/onRejected handlers的闭包上下文

  • 同时因为在闭包上下文中无法获取到async_function_object,所以要将它存入闭包上下文的扩展字段,这样onFulfilled/onRejected handlers执行时就能获取到正确的async_function_object,也就能对其进行resume/throw操作

接下来就是创建onFulfilled/onRejected handlers,在async function里,它们包含了resume/throw执行逻辑,通过它们就能从suspend point恢复或者抛错退出。

resume/throw对应bytecode中ResumeGenerator/Rethrow

其实整个await执行过程,除了在BuildAwait时BuildSuspendPoint引入了suspend point,其他就是纯粹的Promise处理逻辑。接下来就是对wrapper promise实例调用一次PerformPromiseThenImpl,如下代码所示,而此时也会传递上面创建的onFulfilled/onRejected handlers。最后一旦onFulfilled,async function就从await point恢复继续执行,然后继续后面的await point的执行;而一旦onRejected,async function就会在suspend point抛错退出

performPromiseThen(
    promise,
    (res) => resume(<<foo>>, res),
    (err) => throw(<<foo>>, err),
    throwaway
)
复制代码

<>就是async_function_object,作为resume/throw的执行上下文

注意:

事实上,resume/throw在cxx层面是由generator的next/throw来实现,且async function里的return对应了Generator.prototype.return,本质上也是一次await point处理,只不过在return时,generator实例的next方法返回值中done属性为true,表示generator迭代完结,从而停止触发next方法。

小结

OK,目前已经分析了async function在V8中整个执行过程,总结起来它的实现原理是:

  1. 以Generator协程机制为基础,由Promise实现异步执行

  2. 通过promise chain搭配Generator的resume/rethrow达到await“等待”执行的同步效果

相信大家对async/await也有了新的认识,接下来继续来看一些和JavaScript async function有关的优秀源码,比如regenerator-runtime。

regenerator-runtime


在JavaScript业界,提到async/await,就不得不提regenerator-runtime,它是facebook/regenerator中关于async function的runtime实现。

鉴于当前兼容性问题,async function有时不能以原生的方式使用,因此会使用regenerator-runtime来模拟async function的runtime。

facebook/regenerator是一个monorepo,不仅包括regenerator-runtime,也提供了regenerator命令,通过regenerator --include-runtime可以生成自带regenerator-runtime的async function转译代码,如转译后的foo函数,这里省略了regenerator-runtime代码:

functionfoo() {  
    var v;  
    return regeneratorRuntime.async(functionfoo$(_context) {    
        while (1) switch (_context.prev = _context.next) {
            case0:       
                _context.next = 2;        
                return regeneratorRuntime.awrap(42);      
            case2:        
                v = _context.sent;        
                return _context.abrupt("return", v);      
            case4:      
            case"end":        
                return _context.stop();    
        }  
    }, null, null, null, Promise);
}
复制代码

发现async function被转译成regeneratorRuntime.async,同时async function的函数体变成了while(1)无限循环。

往往regenerator转译后的代码给人的印象,甚至说误解:

  • 调用栈紊乱,难以调试,尤其是while(1)无限循环的出现

  • 增加代码大小影响性能

但希望在明白它的实现原理之后能缓解以上这些误解

Generator runtime

之所把它放在async function在V8的实现原理分析之后,是因为regenerator-runtime完美复刻了V8的async function实现原理:

  1. 以Generator协程机制为基础,由Promise实现异步执行

  2. 通过promise chain搭配Generator的resume/rethrow达到await“等待”执行的同步效果

  1. 首先获取AsyncIterator实例iter

  2. 通过promise chain配合iter.next来实现await“等待”效果,因为当AsyncIterator迭代器未完结时就会由promise onFulfilled触发下一次iter.next继续迭代下去

  3. 最后将promise chain返回给调用者

总结

为了帮助大家更好温习重点知识、更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

内容包括html,css,JavaScript,ES6,计算机网络,浏览器,工程化,模块化,Node.js,框架,数据结构,性能优化,项目等等。

包含了腾讯、字节跳动、小米、阿里、滴滴、美团、58、拼多多、360、新浪、搜狐等一线互联网公司面试被问到的题目,涵盖了初中级前端技术点。

前端面试题汇总

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

JavaScript

性能

linux

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值