The async/await in javascript really solves the pain point of async, and is increasingly inseparable from the support of async/await during development. Recently I have been using async/await and wondered what the interrupt recovery implementation of Generator is.
Babel implementation
Babel converts async/await and Generator into js code that is sufficiently compatible.
Code conversion
Convert foo using Babel
async function foo() {
await 111;
await 222;
}
Copy the code
Babel after conversion
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);
}));
Copy the code
- The execution logic is all there
_callee$
As you can seeregeneratorRuntime
An API for the Generator is provided and executed multiple times_callee$
As well asswitch
and_context
To achieve the interruptible recovery effect of Generator. _asyncToGenerator
To automatically execute Generator, so async/await is indeed in BabelGenerator+ automatic actuatorTo achieve.
V8 implementation
There are many javascript engines, and async/await implementations are different. Just look at the async/await implementation in the V8 engine.
V8 parsing process
V8 bytecode
will
async function foo() {
await 111;
await 222;
await 333;
}
Copy the code
Through the command
d8 --print-bytecode ./code.js
// or
node --print-bytecode ./code.js
Copy the code
Converted bytecode
[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: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]>Copy the code
V8 bytecode parsing
- So the first line is going to be
SwitchOnGeneratorState
, which is the bytecode operator. The execution logic is as follows
// 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(a); }Copy the code
If maybe_generator is not null, depending on the JSGeneratorObject: : kContinuationOffset can get into the state, and then according to it from a Constant access to jump in the pool of addresses get relative_jump, To choose to jump to ResumeGenerator. In fact, Babel implementation is similar to the switch meaning.
-
If maybe_generator is empty, run to InvokeIntrinsic [_AsyncFunctionEnter], R1-R2. The AsyncFunctionEnter function generates a generator. It contains the interrupt the recovery information, including JSAsyncFunctionObject: : kContinuationOffset.
-
Then enter AsyncFunctionAwaitUncaught (if await in the trycatch into AsyncFunctionAwaitCaught). AsyncFunctionAwaitUncaught for PerformPromiseThen founded on_resolve and on_reject calls. To invoke the AsyncFunctionAwaitResumeClosure on_resolve and on_reject is resumed. It through ResumeGeneratorTrampoline using assembler code compatible with each platform to realize the specific recovery logic, it is also the generator. The next, and the generator. The realization of the throw.
void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResumeClosure( TNode
context, TNode
{
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.
}
Copy the code
- Continue execution to bytecode
SuspendGenerator
. It sets the location and context to recover next time, then returns. - The next step 3
on_resolve
andon_reject
After the command is executed, re-enterSwitchOnGeneratorState
Jump to based on the location set in step 4ResumeGenerator
To restore the register and accumulator to continue execution.
.
conclusion
Both Babel and V8’s implementation of async/await is surprisingly similar logic. It’s just that v8’s implementation is more low-level and natural.