Handwritten async, await, generator core logic

preface

Some people in the group chatted that they asked about the implementation principle of async in the interview and mentioned generator in the answer. Recently, they just learned three things in the title. They felt that the answer was a bit inappropriate and they had some trouble. Finally, they wrote the general logic of these three things.

There is no detailed source code analysis in this article, which belongs to the way of understanding in the process of personal learning. If you want to learn, you can refer to the links given in this article. If you have any comments / suggestions, please point out.

async

Here are the answers from friends. The problem is the implementation principle of async function. When talking about async, I don't think it has anything to do with generator.

Let's take a look MDN Description on:

async function is used to define an asynchronous function that returns an AsyncFunction object. An asynchronous function is a function that is executed asynchronously through an event loop and returns its result through an implicit Promise.

Take a look MDN Conversion result on:

For example, the following:

async function foo() {
   return 1
}
Copy code

is equivalent to:

function foo() {
   return Promise.resolve(1)
}
Copy code

So on the implementation of async, it should be more similar to the following code:

function _async(fn) {
    return (...args) => Promise.resolve(fn(...args));
}
Copy code

generator

Let's see what the generator is. Generator is implemented first to be used in the following await. Here is Liao Xuefeng Example code in:

function* fib(max) {
    var
        t,
        a = 0,
        b = 1,
        n = 0;
    while (n < max) {
        yield a;
        [a, b] = [b, a + b];
        n ++;
    }
    return;
}

var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}
Copy code

It can be understood that calling the next method on the "object" returned by the generator can produce results similar to the form of {value, done}.

Let's take a look at digging friends Write code like Cai Xuhan Of 9k word | Promise/async/Generator implementation principle analysis The result of generator conversion written in this article:

// code
function* foo() {
  yield 'result1'
  yield 'result2'
  yield 'result3'
}
  
const gen = foo()
console.log(gen.next().value)
console.log(gen.next().value)
console.log(gen.next().value)

// babel official website conversion result
"use strict";

var _marked =
/*#__PURE__*/
regeneratorRuntime.mark(foo);

function foo() {
  return regeneratorRuntime.wrap(function foo$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return 'result1';

        case 2:
          _context.next = 4;
          return 'result2';

        case 4:
          _context.next = 6;
          return 'result3';

        case 6:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

var gen = foo();
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);

Copy code

In the process of group discussion, in order to distinguish async from await, the following "generator" is written:

function get() {
  let g = {
    done: false,
    count: 0,
    next() {
      if (this.count === 3) this.done = true;
      if (this.done) return { value: this.count, done: this.done };
      this.count++;
      return { value: this.count, done: this.done };
    }
  }
  return g;
}

let obj = get();
console.log(obj.next())
console.log(obj.next())
console.log(obj.next())
console.log(obj.next())
console.log(obj.next())

// Output results
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 3, done: true }
// { value: 3, done: true }
Copy code

The above code is quite different from the implementation in babel and ES, but it is basically the execution logic of a generator. For details, please refer to switch (_ context.prev = context.next )And a few lines_ context.next =2; code.

await

Let's review the function of await, which can transform asynchronous code into synchronous code logic. Before the return value of asynchronous code arrives, the program will be suspended. So we need to choose a code that can make the program hang, that is, while(true). The implementation is as follows:

function _await() {
    let result = data.next();
    while (true) {
      console.log('waiting...', result); 
      if (result.done) return result.value;
      result = data.next();
    }
}

let g = get();
console.log('before');
let a = myAwait(g);
console.log(a);
console.log('after');

// output
// before
// awaiting... { value: 1, done: false }
// awaiting... { value: 2, done: false }
// awaiting... { value: 3, done: false }
// awaiting... { value: 3, done: true }
// 3
// after
Copy code

further more

In the actual use process, await can only be used in async. The group friend asked how to implement this, so he tossed out the following version.

function myAwait() {
  this.c = function(data) {
    if (!this.isCalledByAsync) throw new Error('Should be called by async');
    let result = data.next();
    while (true) {
      console.log('awaiting...', result); 
      if (result.done) return result.value;
      result = data.next();
    }
  }
}

function myAsync(fn) {
  myAwait.prototype.isCalledByAsync = true;
  let m = get();
  console.log('async before');
  let d = new myAwait().c(m);
  console.log(d);
  console.log('async after');
  myAwait.prototype.isCalledByAsync = false;
}
myAsync();

let g = get();
console.log('no async before');
let a = new myAwait().c(g);
console.log(a);
console.log('no async after');

// output
// async before
// awaiting... { value: 1, done: false }
// awaiting... { value: 2, done: false }
// awaiting... { value: 3, done: false }
// awaiting... { value: 3, done: true }
// 3
// async after
// no async before
// Error: Should be called by async
Copy code

I didn't think of any other way to make this mark. I can only use the way of controlling the prototype to control whether it can be executed, but it can only be used inside async.

epilogue

This article is the understanding in the process of personal learning. If there is something wrong with the understanding, you are welcome to give advice. This article is also written for you to learn the code process to add some different perspectives.

Posted by garethhall on Wed, 20 May 2020 22:59:02 -0700