Async functions realize asynchronous code in a nearly synchronous way, readable, is very convenient to use.

Its essence is the syntactic sugar of generator function, this article tries to replace async function by writing a function, remove the sugar coating and see.

asyncFunction of the demo

Let’s start with a demo of async functions:

const getData = () = >
  new Promise((resolve) = > setTimeout(() = > resolve('data'), 1000));

async function test() {
  const data = await getData();
  console.log('data: ', data);
  const data2 = await getData();
  console.log('data2: ', data2);
  return 'success';
}

// Such a function should print data after 1 second, data2 after 1 second, and success
test().then((res) = > console.log(res));
Copy the code

Switch to a demo of generator functions

Each async function executes a generator function, and converting to a generator function is easy:

  • To get rid ofasync
  • add*
  • willawaitSwitch toyield

Example translation:

const getData = () = >
  new Promise((resolve) = > setTimeout(() = > resolve('data'), 1000));

function* testGen() {
  const data = yield getData();
  console.log('data: ', data);
  const data2 = yield getData();
  console.log('data2: ', data2);
  return 'success';
}
Copy the code

The basics of generator functions

A generator function is a function, but it returns an iterator.

If you run the entire generator function, the number of calls to Next = yield + 1

Let’s start with a simple demo:

function* gen() {
  const data = yield 'first yield';
  console.log('data: ', data);
  const data2 = yield 'Second yield';
  console.log('data2: ', data2);
  return 'success';
}

const it = gen();
var { value, done } = it.next('First next');
var { value, done } = it.next('Second next');
var { value, done } = it.next('Third next');
Copy the code

Understanding the next and yield statements is especially critical

  • nextLet the code inside the function start executing,nextIs itself a function,
    • itsThe return valueIs an object, number onenThe return value of the next function is the firstnayieldWhat follows, or the return value of the generator function,doneIs to indicate whether the function has finished executing.
    • But the firstnayieldIs replaced by the firstn+1aThe parameters of the next
  • nextDuring execution, encounteredyieldIs suspended

The generator function is expressed in pseudocode as follows:

In the pseudocode, I’m going to replace the yield and the next, and I’m going to put a pause point, so I think it makes sense

function* gen() {
  // const data = yield' first yield';
  const data = 'Second next'; // The second next argument, which is also the first pause, is not assigned
  console.log('data: ', data);
  // const data2 = yield' second yield';
  const data2 = 'Third next'; // The third next argument, which is also the second pause, is not assigned
  console.log('data2: ', data2);
  return 'Return value of generator function'; // The generator function is finished
}
// it is iterator
const it = gen();
// The first next function returns the value after the first yield
// var {value, done} = it. Next (' next');
var value = 'first yield',
  done = false; // The first next function returns the value after the first yield
// var {value, done} = it. Next (' second next');
var value = 'Second yield',
  done = false; // The second next function returns the value after the second yield
// var {value, done} = it. Next (' next');
var value = 'Return value of generator function',
  done = true; // Note that done is true, so value is the return value of the generator function
Copy the code

So, when you encounter a yield assignment, be sure to remind yourself that it has nothing to do with what follows the yield!

When you encounter a next assignment, value has nothing to do with the next argument.

Let the generator do it manually

Back to the example of the main generator function:

const getData = () = >
  new Promise((resolve) = > setTimeout(() = > resolve('data'), 1000));

function* testGen() {
  const data = yield getData(); // data = the second next argument
  console.log('data: ', data);
  const data2 = yield getData(); // data = the third next argument
  console.log('data2: ', data2);
  return 'success';
}
Copy the code

If you want data to be data, it doesn’t have to do with yield, it has to do with passing in next.

const it = testGen();
// Value is the first yield and getData() is the promise instance,
let res = it.next();
let promise = res.value;
let done = res.done;
promise.then((data) = > {
  // Promise is the second yield followed by getData(), which is actually the promise instance. Note that the next argument here is the assignment to data above
  res = it.next(data);
  promise = res.value;
  done = res.done;
  promise.then((data) = > {
    // Done is true, and promise is the return value of the generator function. Note that the next argument here is the assignment to data2 above
    res = it.next(data);
    promise = res.value;
    done = res.done;
  });
});
Copy the code

Let the generator function execute automatically

Wrapping the above procedure into a function that automatically executes the generator function can take out the repetitions:

function co(gen, ... args) {
  return (. args) = > {
    constit = gen(... args);// First run
    let res = it.next();
    let promise = res.value;
    let done = res.done;
    // Repeat part encapsulation:
    const fn = () = > {
      if (done) {
        return Promise.resolve(promise);
      }
      promise.then((data) = > {
        // Done is true, and promise is the return value of the generator function. Note that the next argument here is the assignment to data2 above
        res = it.next(data);
        promise = res.value;
        done = res.done;
        // Keep walking
        fn();
      });
    };
    fn();
  };
}
co(testGen)();
Copy the code

Better version

It’s a little more complicated, considering the anomalies.

The simplest implementation of handling handwriting async await directly here (20 lines)

function asyncToGenerator(generatorFunc) {
  return function () {
    const gen = generatorFunc.apply(this.arguments);
    return new Promise((resolve, reject) = > {
      function step(key, arg) {
        let generatorResult;
        try {
          generatorResult = gen[key](arg);
        } catch (error) {
          return reject(error);
        }
        const { value, done } = generatorResult;
        if (done) {
          return resolve(value);
        } else {
          return Promise.resolve(value).then(
            (val) = > step('next', val),
            (err) = > step('throw', err)
          );
        }
      }
      step('next');
    });
  };
}
Copy the code

Ideas:

function asyncToGenerator(generatorFunc) {
  // Returns a new function
  return function() {
  
    // Call generator to generate iterators
    Var gen = testG()
    const gen = generatorFunc.apply(this.arguments)

    // Return a promise because the outside is using the return value of this function either as.then or await
    // var test = asyncToGenerator(testG)
    // test().then(res => console.log(res))
    return new Promise((resolve, reject) = > {
    
      // Internally define a step function to override the yield barrier step by step
      // Key has two values, next and throw, corresponding to gen's next and throw methods respectively
      The arg argument is used to yield the promise resolve value to the next yield
      function step(key, arg) {
        let generatorResult
        
        // This method needs to be wrapped in a try catch
        // If an error occurs, discard the promise, reject the error, and catch the error
        try {
          generatorResult = gen[key](arg)
        } catch (error) {
          return reject(error)
        }

        // gen.next() results in a {value, done} structure
        const { value, done } = generatorResult

        if (done) {
          // If this is already done, resolve the promise
          // This done will not be true until the last call to next
          {done: true, value: 'success'}
          // This value is the return value of the generator function
          return resolve(value)
        } else {
          // Call gen.next() every time except at the end
          {value: Promise, done: false}
          Resolve accepts a Promise as an argument
          // Then will only be called when the promise argument is resolved
          return Promise.resolve(
            // This value corresponds to the promise after yield
            value
          ).then(
            // When the value promise is resove, next is executed
            // And whenever done is not true, the promise is recursively unwrapped
            // Next ().value.then(value => {
            // gen.next(value).value.then(value2 => {
            // gen.next()
            //
            // // now done is true and the entire promise is resolved
            // // the most external test().then(res => console.log(res)) then starts execution
            / /})
            // })
            function onResolve(val) {
              step("next", val)
            },
            // If promise is rejected, enter step again
            // The difference is that this try catch calls Gen. throw(err).
            // Then you catch the promise, reject it
            function onReject(err) {
              step("throw", err)
            },
          )
        }
      }
      step("next")}}}Copy the code

reference

  • Minimal implementation of Handwriting Async await (20 lines)