“This is the 23rd day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021”

preface

Async /await is a syntactic sugar that is associated with promises and generators. So I will introduce its usage and generator before implementing async/await.

So what is grammatical sugar? Grammatical sugar is more complex meaning expressed in more concise words. Similar to Chinese idioms, I use four-character idioms to express a sentence. So syntactic sugar is a simple way to implement complex methods.

The use of the async/await

Async /await can be used to perform asynchronous operations with synchronous methods, for example

function getData(nums) {
  return new Promise(resolve= > {
    setTimeout(() = > {
      resolve(nums * 2)},1000)})}async function fn() {
 const data = await getData(1)
 console.log(data); // Prints 2 after 1s
}
fn();
Copy the code

The code following the “await” method will be suspended until the getData method has finished executing, and execution will continue after execution. If await can also be followed by a normal function, it will run directly.

The async method returns a Promise

async function fn () {}
console.log(fn) // [AsyncFunction: fn]
console.log(fn()) // Promise {<fulfilled>: undefined}
Copy the code

Summary.

  • Await can only be used in async functions, otherwise an error will be reported
  • Async returns a Promise object with or without a return value
  • “Await” is best followed by a Promise, although other values will queue as well
  • Async /await is to perform asynchronous operations in a synchronous manner

The use of the generator

Generator generator is a new feature of the ES6 specification that allows us to pause a function wherever it is executing and continue it later when it is appropriate to use the function.

If it looks familiar, await can also cause a function to pause at the point where it is executing and continue after execution, so async/await is a syntactic sugar for generator. Of course there are big differences, so let’s take a look at how generator is used.

function getData(num) {
  return num
}
function* gen() {
  const a = yield getData(1)
  const b = yield getData(2)
  const c = yield getData(3)}const g = gen() / / generated iterator
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: undefined, done: true }
Copy the code

We use * to indicate that this is a generator method and use yield to indicate the pause point. Generator methods cannot execute automatically and must call the next method to proceed from one pause point to the next until the end of the run. In the example above, we have defined three pause points. On the first three executions, the done attribute returns false and the value attribute returns the method’s return value. The fourth time next’s done becomes true, the execution is complete.

If a parameter is passed when using the next method, it is treated as the yield statement return value. Note that the first next pass is useless, but only from the second next pass is useful, because when next passes values, it yields on the right and receives arguments on the left.

So in the following example, the second pass from Next will be assigned to A, the yield getData(1) return; The third pass from next is assigned to B; The fourth pass from Next is assigned to C.

function getData(num) {
  return num
}
function* gen() {
  const a = yield getData(1)
  console.log('a:',a); // a: b
  const b = yield getData(2)
  console.log('b:',b); // b: c
  const c = yield getData(3)
  console.log('c:',c); // c: d
}
const g = gen() / / generated iterator
console.log(g.next('a')) // { value: 1, done: false }
console.log(g.next('b')) // { value: 2, done: false }
console.log(g.next('c')) // { value: 3, done: false }
console.log(g.next('d')) // { value: undefined, done: true }
Copy the code

Implement async/await

We said async/await is the syntactic sugar of generator. Yied inside has await like pause functionality, so our implementation can use generator functions. First analyze the differences between them.

  • Generator functions do not return a Promise. Async functions return a Promise
  • Generator functions do not support self-execution and need to perform corresponding operations to have the same queuing effect as Async
  • Operations performed by generator functions are imperfect because there is no certainty of how many yields there are or how many times they will be nested

Once we resolve these three differences, we will have achieved the desired effect.

First, a generator function returns a value that is not a Promise, so we need to wrap a higher-order function, take a generator function, and, through some processing, return a function with async functions.

function generatorToAsync(generatorFunc) {
  return function() {
    return new Promise((resolve, reject) = >{})}}Copy the code

That solves the first difference.

The second and third difference is that generator functions do not support self-execution and there is no definite yield on which we can perform some operations on the above framework.

function generatorToAsync(generatorFunc) {
  return function() {
    // First call generator to generate iterators
    const gen = generatorFunc.apply(this.arguments)
    
    // Return a promise
    return new Promise((resolve, reject) = > {
      // internally define a step recursive function
      function step(key, arg) {
        let generatorResult
        try {
          // Execute next or throw if an error is reported
          generatorResult = gen[key](arg)
        } catch (error) {
          return reject(error)
        }
        const { value, done } = generatorResult
        if (done) {
          // After all yIED execution is complete, call resolve
          return resolve(value)
        } else {
          return Promise.resolve(value).then(val= > step('next', val), err= > step('throw', err))
        }
      }
      step("next")}}}Copy the code

Yied statements are called one by one by executing the generator function to generate the iterator, then defining a recursive step method in the returned Promise and running the next method until it terminates when the done attribute of the returned value is true.

Let’s contrast this with async/await.

// async/await
async function fn() {
  const num1 = await getData(1);
  console.log(num1); // returns 2 after 1s
  const num2 = await getData(2);
  console.log(num2); // return 4 after 2s
  const num3 = await getData(4);
  console.log(num3); // return 8 after 3s
}
const asyncRes = fn();
console.log(asyncRes); // Promise {<pending>}
asyncRes.then(res= > {
  console.log(10); // return 10 after 3s
})
Copy the code

The generator version

function* gen() {
  const num1 = yield getData(1)
  console.log(num1) / / 2
  const num2 = yield getData(2)
  console.log(num2) / / 4
  const num3 = yield getData(4)
  console.log(num3) / / 8
  return 10
}

const asyncRes = generatorToAsync(gen)()
console.log(asyncRes); // Promise {<pending>}
asyncRes.then(res= > {
  console.log(10); // return 10 after 3s
})
Copy the code

The return value is exactly the same as the effect. At this point we have completed the implementation of async/await.