Preliminary knowledge

  • Promise
  • Generator
  • Generator function
  • Async function
  • Choose the correct JavaScript asynchronous method

Async/await rules

  • Await must appear after async declared functions
  • If the value following await is a Promise, await will wait for the normal processing of the Promise to complete and return the result of its processing
  • Await if the value following await is not a Promise, await converts the value to a normally processed Promise and waits for its processing result
  • After the await, the code behind the await will be placed in the microtask queue

Generator functions and automatons represent async/await

The async function documentation reads:

The purpose of async/await is to simplify the syntax necessary to consume promise-based APIs. The behavior of async/await is similar to combining generators and promises.

The purpose of async/await is to simplify the syntax needed to use a Promise based API. Async /await behaves as if a generator and promise are used together

It looks like a combination of generators and promises. I found the following code on the Internet, which perfectly explains the use of generators and promises.

async function fn(args){
  // await ...
  // await ...
  // await ...
}

/ / is equivalent to
function fn(args){
  function spawn(genF) {
    return new Promise(function(resolve, reject) {
        var gen = genF();
        function step(nextF) {
            try {
                var next = nextF();
            } catch(e) {
                return reject(e);
            }
            if(next.done) {
                return resolve(next.value);
            }
            Promise.resolve(next.value).then(function(v) {
                step(function() {
                    return gen.next(v);
                });
            }, function(e) {
                step(function() {
                    return gen.throw(e);
                });
            });
        }
        step(function() {
            return gen.next(undefined);
        });
    });
  }

  return spawn(function* () {
    // yield ...
    // yield ...
    // yield ...
  });
}
fn()
Copy the code

The idea behind this code is to wrap a Generator and an autoexecutor in a single function.

  1. The fn function returns the result of the execution of a new function spawn;
  2. The spawn function argument is a generator function
  3. Spawn returns a Promise, and async returns a Promise;
let hello = async() = > {return "Hello" };
hello().then((value) = > console.log(value))
// output: Hello

let helloWorld = async() = > {await "Hello"
    await "world"
    return "Nick"
};
helloWorld().then((value) = > console.log(value))
// output: Nick
Copy the code
  1. Spawn creates a generator internallyvar gen = genF();
  2. Spawn will execute step and take a function, nextF;
  • The first execution, next(undefined), does not pass values to the generator
  • If the generator does not end execution, step encapsulates the value produced by the current generator, the return value of yield, as a Promise with promise.resolve
    • If yield returns a Promise, the original Promise is returned
    • If the value is thenable (that is, with the “then” method), the returned promise will “follow “the thenable object and adopt its final state
    • Otherwise the returned promise will be fulfilled with this value
  • When a Promise is complete, the code following yield is put on the microtask queue
  • When the microtask is executed, the next method is executed again, passing the last value to the generator
  • The loop executes until the generator ends

This creates an async/await effect with Generator functions and an automatic executor, so async/await is the syntactic sugar of Generator

The source code parsing

AST spec

interface Function <: Node {
  id: Identifier | null;
  params: [ Pattern ];
  body: BlockStatement;
  generator: boolean;
  async: boolean;
}
Copy the code

The plug-in code

export default declare((api, options) = > {
  api.assertVersion(7);

  const { method, module } = options;

  if (method && module) {
    return {
      name: "transform-async-to-generator".visitor: {
        Function(path, state) {
          if(! path.node.async || path.node.generator)return;

          let wrapAsync = state.methodWrapper;
          if (wrapAsync) {
            wrapAsync = t.cloneNode(wrapAsync);
          } else {
            wrapAsync = state.methodWrapper = addNamed(path, method, module); } remapAsyncToGenerator(path, { wrapAsync }); ,}}}; }return {
    name: "transform-async-to-generator".visitor: {
      Function(path, state) {
        if(! path.node.async || path.node.generator)return;

        remapAsyncToGenerator(path, {
          wrapAsync: state.addHelper("asyncToGenerator")}); ,}}}; });// remapAsyncToGenerator
export default function (
  path: NodePath,
  helpers: { wrapAsync: Object, wrapAwait: Object },
) {
  path.traverse(awaitVisitor, {
    wrapAwait: helpers.wrapAwait,
  });

  const isIIFE = checkIsIIFE(path);

  path.node.async = false;
  path.node.generator = true;

  wrapFunction(path, t.cloneNode(helpers.wrapAsync));

  const isProperty =
    path.isObjectMethod() ||
    path.isClassMethod() ||
    path.parentPath.isObjectProperty() ||
    path.parentPath.isClassProperty();

  if(! isProperty && ! isIIFE && path.isExpression()) { annotateAsPure(path); }}Copy the code

Code main work through first! Path. The node. The async | | path. The node. The generator to judge if not async statement functions or the generator is returned. Otherwise, call babel-helper-remap-async-to-generator to turn async into generator. If the function is executed immediately, there is additional processing.

UT

// input
(async function() { await 'ok'}) (); (async function notIIFE() { await 'ok' });

// output
babelHelpers.asyncToGenerator(function* () {
  yield 'ok'; }) ();/*#__PURE__*/
(function () {
  var _notIIFE = babelHelpers.asyncToGenerator(function* () {
    yield 'ok';
  });

  function notIIFE() {
    return _notIIFE.apply(this.arguments);
  }

  returnnotIIFE; }) ();Copy the code

As can be seen from the UT async/await be turned babelHelpers. AsyncToGenerator look again at what asyncToGenerator looks like:

helpers.asyncToGenerator = helper("7.0.0 - beta. 0")` 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); } } export default 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); }); }; } `;
Copy the code

This is the same code as Generator functions and automatic executors representing async/await above.

summary

The purpose of async/await is to simplify the syntax required when using promise-based apis, and is arguably the syntactic sugar of Generator. Babel is a way of converting async/await to generator and automatic executor via plugin-transform-async-to-generator.