javascript中的async/await着实解决了异步的痛点,在开发的时候也越来越离不开async/await的加持。最近在使用的时候突然想知道async/await以及Generator的中断恢复实现是怎样的。
babel实现
babel将async/await以及Generator转换成足够兼容的js代码。
代码转换
使用babel转换foo
async function foo() {
await 111;
await 222;
}
babel转换后
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
function foo() {
return _foo.apply(this, arguments);
}
function _foo() {
_foo = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 111;
case 2:
_context.next = 4;
return 222;
case 4:
case "end":
return _context.stop();
}
}
}, _callee);
}));
- 执行逻辑都在
_callee$中,可以看到regeneratorRuntime提供了Generator的api,并且通过多次执行_callee$以及switch和_context来实现了Generator的可中断恢复效果。 _asyncToGenerator来自动执行Generator,所以async/await在babel中确实是 Generator+自动执行器 来实现的。
v8实现
javascript引擎有很多种,async/await实现方法也不一样。这里只看一下v8引擎中async/await的实现。
v8的解析过程
v8字节码
将
async function foo() {
await 111;
await 222;
await 333;
}
通过命令
d8 --print-bytecode ./code.js
// or
node --print-bytecode ./code.js
转换字节码
[generated bytecode for function: foo (0x003805873b21 <SharedFunctionInfo foo>)]
Parameter count 1
Register count 6
Frame size 48
0000003805874306 @ 0 : ae fb 00 03 SwitchOnGeneratorState r0, [0], [3] { 0: @35, 1: @73, 2: @111 }
000000380587430A @ 4 : 27 fe fa Mov <closure>, r1
000000380587430D @ 7 : 27 02 f9 Mov <this>, r2
18 E> 0000003805874310 @ 10 : 64 02 fa 02 InvokeIntrinsic [_AsyncFunctionEnter], r1-r2
0000003805874314 @ 14 : 26 fb Star r0
0000003805874316 @ 16 : 27 ff fa Mov <context>, r1
28 S> 0000003805874319 @ 19 : 0c 6f LdaSmi [111]
000000380587431B @ 21 : 26 f8 Star r3
000000380587431D @ 23 : 27 fb f9 Mov r0, r2
0000003805874320 @ 26 : 64 01 f9 02 InvokeIntrinsic [_AsyncFunctionAwaitUncaught], r2-r3
28 E> 0000003805874324 @ 30 : af fb fb 02 00 SuspendGenerator r0, r0-r1, [0]
0000003805874329 @ 35 : b0 fb fb 02 ResumeGenerator r0, r0-r1
000000380587432D @ 39 : 26 f9 Star r2
000000380587432F @ 41 : 64 0b fb 01 InvokeIntrinsic [_GeneratorGetResumeMode], r0-r0
0000003805874333 @ 45 : 26 f8 Star r3
0000003805874335 @ 47 : 0b LdaZero
0000003805874336 @ 48 : 6d f8 TestReferenceEqual r3
0000003805874338 @ 50 : 99 05 JumpIfTrue [5] (000000380587433D @ 55)
000000380587433A @ 52 : 25 f9 Ldar r2
000000380587433C @ 54 : a9 ReThrow
44 S> 000000380587433D @ 55 : 00 0c de 00 LdaSmi.Wide [222]
0000003805874341 @ 59 : 26 f8 Star r3
0000003805874343 @ 61 : 27 fb f9 Mov r0, r2
0000003805874346 @ 64 : 64 01 f9 02 InvokeIntrinsic [_AsyncFunctionAwaitUncaught], r2-r3
44 E> 000000380587434A @ 68 : af fb fb 02 01 SuspendGenerator r0, r0-r1, [1]
000000380587434F @ 73 : b0 fb fb 02 ResumeGenerator r0, r0-r1
0000003805874353 @ 77 : 26 f9 Star r2
0000003805874355 @ 79 : 64 0b fb 01 InvokeIntrinsic [_GeneratorGetResumeMode], r0-r0
0000003805874359 @ 83 : 26 f8 Star r3
000000380587435B @ 85 : 0b LdaZero
000000380587435C @ 86 : 6d f8 TestReferenceEqual r3
000000380587435E @ 88 : 99 05 JumpIfTrue [5] (0000003805874363 @ 93)
0000003805874360 @ 90 : 25 f9 Ldar r2
0000003805874362 @ 92 : a9 ReThrow
60 S> 0000003805874363 @ 93 : 00 0c 4d 01 LdaSmi.Wide [333]
0000003805874367 @ 97 : 26 f8 Star r3
0000003805874369 @ 99 : 27 fb f9 Mov r0, r2
000000380587436C @ 102 : 64 01 f9 02 InvokeIntrinsic [_AsyncFunctionAwaitUncaught], r2-r3
60 E> 0000003805874370 @ 106 : af fb fb 02 02 SuspendGenerator r0, r0-r1, [2]
0000003805874375 @ 111 : b0 fb fb 02 ResumeGenerator r0, r0-r1
0000003805874379 @ 115 : 26 f9 Star r2
000000380587437B @ 117 : 64 0b fb 01 InvokeIntrinsic [_GeneratorGetResumeMode], r0-r0
000000380587437F @ 121 : 26 f8 Star r3
0000003805874381 @ 123 : 0b LdaZero
0000003805874382 @ 124 : 6d f8 TestReferenceEqual r3
0000003805874384 @ 126 : 99 05 JumpIfTrue [5] (0000003805874389 @ 131)
0000003805874386 @ 128 : 25 f9 Ldar r2
0000003805874388 @ 130 : a9 ReThrow
0000003805874389 @ 131 : 0d LdaUndefined
000000380587438A @ 132 : 26 f8 Star r3
000000380587438C @ 134 : 10 LdaTrue
000000380587438D @ 135 : 26 f7 Star r4
000000380587438F @ 137 : 27 fb f9 Mov r0, r2
0000003805874392 @ 140 : 64 04 f9 03 InvokeIntrinsic [_AsyncFunctionResolve], r2-r4
72 S> 0000003805874396 @ 144 : aa Return
0000003805874397 @ 145 : 26 f9 Star r2
0000003805874399 @ 147 : 83 f9 03 CreateCatchContext r2, [3]
000000380587439C @ 150 : 26 fa Star r1
000000380587439E @ 152 : 0f LdaTheHole
000000380587439F @ 153 : a7 SetPendingMessage
00000038058743A0 @ 154 : 25 fa Ldar r1
00000038058743A2 @ 156 : 16 f9 PushContext r2
00000038058743A4 @ 158 : 1b 02 LdaImmutableCurrentContextSlot [2]
00000038058743A6 @ 160 : 26 f7 Star r4
00000038058743A8 @ 162 : 10 LdaTrue
00000038058743A9 @ 163 : 26 f6 Star r5
00000038058743AB @ 165 : 27 fb f8 Mov r0, r3
00000038058743AE @ 168 : 64 03 f8 03 InvokeIntrinsic [_AsyncFunctionReject], r3-r5
72 S> 00000038058743B2 @ 172 : aa Return
Constant pool (size = 4)
00000038058742A1: [FixedArray] in OldSpace
- map: 0x020373f40729 <Map>
- length: 4
0: 35
1: 73
2: 111
3: 0x003805874249 <ScopeInfo CATCH_SCOPE [5]>
Handler Table (size = 16)
from to hdlr (prediction, data)
( 19, 145) -> 145 (prediction=4, data=1)
Source Position Table (size = 20)
0x0038058743b9 <ByteArray[20]>
v8字节码解析
- 首先第一行便是
SwitchOnGeneratorState,它是字节码操作符。执行逻辑如下
// SwitchOnGeneratorState <generator> <table_start> <table_length>
//
// If |generator| is undefined, falls through. Otherwise, loads the
// generator's state (overwriting it with kGeneratorExecuting), sets the context
// to the generator's resume context, and performs state dispatch on the
// generator's state by looking up the generator state in a jump table in the
// constant pool, starting at |table_start|, and of length |table_length|.
IGNITION_HANDLER(SwitchOnGeneratorState, InterpreterAssembler) {
TNode<Object> maybe_generator = LoadRegisterAtOperandIndex(0);
Label fallthrough(this);
GotoIf(TaggedEqual(maybe_generator, UndefinedConstant()), &fallthrough);
TNode<JSGeneratorObject> generator = CAST(maybe_generator);
TNode<Smi> state =
CAST(LoadObjectField(generator, JSGeneratorObject::kContinuationOffset));
TNode<Smi> new_state = SmiConstant(JSGeneratorObject::kGeneratorExecuting);
StoreObjectField(generator, JSGeneratorObject::kContinuationOffset,
new_state);
TNode<Context> context =
CAST(LoadObjectField(generator, JSGeneratorObject::kContextOffset));
SetContext(context);
TNode<UintPtrT> table_start = BytecodeOperandIdx(1);
// TODO(leszeks): table_length is only used for a CSA_ASSERT, we don't
// actually need it otherwise.
TNode<UintPtrT> table_length = BytecodeOperandUImmWord(2);
// The state must be a Smi.
CSA_ASSERT(this, TaggedIsSmi(state));
TNode<IntPtrT> case_value = SmiUntag(state);
CSA_ASSERT(this, IntPtrGreaterThanOrEqual(case_value, IntPtrConstant(0)));
CSA_ASSERT(this, IntPtrLessThan(case_value, table_length));
USE(table_length);
TNode<WordT> entry = IntPtrAdd(table_start, case_value);
TNode<IntPtrT> relative_jump = LoadAndUntagConstantPoolEntry(entry);
Jump(relative_jump);
BIND(&fallthrough);
Dispatch();
}
如果maybe_generator不为空,则根据JSGeneratorObject::kContinuationOffset能够获取到state,然后根据它从Constant pool中获取到要跳转的地址获取到relative_jump,来选择跳到ResumeGenerator。其实跟babel实现中的switch是差不多的意思。
-
如果
maybe_generator为空,则运行到InvokeIntrinsic [_AsyncFunctionEnter], r1-r2。AsyncFunctionEnter函数便是生成一个generator。它包含了中断恢复所需信息,包括JSAsyncFunctionObject::kContinuationOffset。 -
接着进入
AsyncFunctionAwaitUncaught(如果在trycatch中的await则进入AsyncFunctionAwaitCaught)。AsyncFunctionAwaitUncaught为PerformPromiseThen的调用创建了on_resolve和on_reject。on_resolve和on_reject便是调用了AsyncFunctionAwaitResumeClosure来恢复运行。它通过ResumeGeneratorTrampoline使用汇编代码兼容各个平台来实现具体的恢复逻辑,它同时也是generator.next和generator.throw的实现。
void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResumeClosure(
TNode<Context> context, TNode<Object> sent_value,
JSGeneratorObject::ResumeMode resume_mode) {
DCHECK(resume_mode == JSGeneratorObject::kNext ||
resume_mode == JSGeneratorObject::kThrow);
TNode<JSAsyncFunctionObject> async_function_object =
CAST(LoadContextElement(context, Context::EXTENSION_INDEX));
// Push the promise for the {async_function_object} back onto the catch
// prediction stack to handle exceptions thrown after resuming from the
// await properly.
Label if_instrumentation(this, Label::kDeferred),
if_instrumentation_done(this);
Branch(IsDebugActive(), &if_instrumentation, &if_instrumentation_done);
BIND(&if_instrumentation);
{
TNode<JSPromise> promise = LoadObjectField<JSPromise>(
async_function_object, JSAsyncFunctionObject::kPromiseOffset);
CallRuntime(Runtime::kDebugAsyncFunctionResumed, context, promise);
Goto(&if_instrumentation_done);
}
BIND(&if_instrumentation_done);
// Inline version of GeneratorPrototypeNext / GeneratorPrototypeReturn with
// unnecessary runtime checks removed.
// Ensure that the {async_function_object} is neither closed nor running.
CSA_SLOW_ASSERT(
this, SmiGreaterThan(
LoadObjectField<Smi>(async_function_object,
JSGeneratorObject::kContinuationOffset),
SmiConstant(JSGeneratorObject::kGeneratorClosed)));
// Remember the {resume_mode} for the {async_function_object}.
StoreObjectFieldNoWriteBarrier(async_function_object,
JSGeneratorObject::kResumeModeOffset,
SmiConstant(resume_mode));
// Resume the {receiver} using our trampoline.
Callable callable = CodeFactory::ResumeGenerator(isolate());
CallStub(callable, context, sent_value, async_function_object);
// The resulting Promise is a throwaway, so it doesn't matter what it
// resolves to. What is important is that we don't end up keeping the
// whole chain of intermediate Promises alive by returning the return value
// of ResumeGenerator, as that would create a memory leak.
}
- 继续执行到字节码
SuspendGenerator。它设置了下次要恢复的位置和上下文,然后return。 - 等第3步
on_resolve和on_reject执行后,重新进入SwitchOnGeneratorState根据第4步设置的位置跳转到ResumeGenerator来恢复寄存器和累加器继续执行。 ...
总结
无论是babel还是v8对async/await的实现竟然是差不多的逻辑。只不过v8实现更底层更自然一些。