看一下 async/await v8 与 babel 中的实现原理

796 阅读9分钟

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字节码解析

  1. 首先第一行便是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是差不多的意思。

  1. 如果maybe_generator为空,则运行到InvokeIntrinsic [_AsyncFunctionEnter], r1-r2AsyncFunctionEnter函数便是生成一个generator。它包含了中断恢复所需信息,包括JSAsyncFunctionObject::kContinuationOffset

  2. 接着进入AsyncFunctionAwaitUncaught(如果在trycatch中的await则进入AsyncFunctionAwaitCaught)。AsyncFunctionAwaitUncaughtPerformPromiseThen的调用创建了on_resolveon_rejecton_resolveon_reject便是调用了AsyncFunctionAwaitResumeClosure来恢复运行。它通过ResumeGeneratorTrampoline使用汇编代码兼容各个平台来实现具体的恢复逻辑,它同时也是generator.nextgenerator.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.
}
  1. 继续执行到字节码SuspendGenerator。它设置了下次要恢复的位置和上下文,然后return。
  2. 等第3步on_resolveon_reject执行后,重新进入SwitchOnGeneratorState根据第4步设置的位置跳转到ResumeGenerator来恢复寄存器和累加器继续执行。 ...

总结

无论是babel还是v8对async/await的实现竟然是差不多的逻辑。只不过v8实现更底层更自然一些。