ES7 specification async await principle

What is async/await?

Async /await is a new specification introduced in ES7 called coroutines. The official description of Ctrip is as follows: A thread can have multiple coroutines, but only one can be executed at the same time.

What is the relationship between async/await and generator/promise

Async /await is the syntactic sugar of the generator/ Promise implementation mechanism. In other words, the nature of async/await is just a combination of generator/promise. I will not go into details about promise. If you are not familiar with promise, you can go to the official website by yourself.

Implement an async/await manually

Let’s start with a standard async/await function:

async function foo() {
  const res = await 'generator';
  console.log(res) / / return the generator
}

foo()
Copy the code

The async keyword indicates that the function is a coroutine function and the return value of the function is promise, while the await keyword is equivalent to the yield of Generaor

What does a generator look like?

function* foo() {
  const res = yield 'generator';
  console.log(res); // Return an object {done: false, value: 'generator'}
}

foo().next(); // Generator function returns yield value method
Copy the code

We’re modifying this code

function* foo() {
  const res = yield Promise.resolve('generator');
  console.log(res); {done: false, value: Promise}
}

foo().next(); // Generator function returns yield value method
Copy the code

Our generator returns a Promise function, rather than a result string like async/await, which we could do:

function* foo() {
  const res = yield Promise.resolve('generator');
  res.then((str) = > {
    console.log(str); / / return the generator
  });
}

foo().next(); // Generator function returns yield value method
Copy the code

We get the result we want, but it’s cumbersome and the code isn’t pretty, so if you add a yield that returns a Promise, you can imagine the result. So how can we return the desired result directly like async/await? Let’s then modify the foo function:

function* foo() {
  const res = yield Promise.resolve('generator');

  console.log(res); / / return the generator
}

function co(gen) {
  gen = gen();
  result = gen.next();

  return result.value.then((res) = > {
    return gen.next(res);
  });
}

co(foo);
Copy the code

After further modification, we can also get generator result strings directly in foo like async/await, which successfully simulates async/await sugar. Let’s go through the code step by step:

function co(gen) {
  gen = gen();
  const result = gen.next(); {done: false, value: Promise}

  return result.value.then((res) = > {
    // Execute the Promise function
    return gen.next(res); The next(param) method of the generator supports passing an argument, and returns the value of the argument to the variable declared before yield. Const res = yield promise.resolve ('generator') in foo above; The res is used to receive the param value in next(param)
  });
}
Copy the code

The co function allows you to pass in an argument, which is clearly our foo coroutine function, so you can declare a result function to receive the return value, and thus achieve the basic function async/await. Let’s test it:

function co(gen) {
  gen = gen();
  const result = gen.next(); {done: false, value: Promise}

  return result.value.then((res) = > {
    // Execute the Promise function
    return gen.next(res); The next(param) method of the generator supports passing an argument, and returns the value of the argument to the variable declared before yield. Const res = yield promise.resolve ('generator') in foo above; The res is used to receive the param value in next(param)
  });
}

function* foo() {
  const res = yield Promise.resolve(1);
  console.log(res); / / returns 1
}

// Execute the co function

co(foo);
Copy the code

We can see that foo is similar to the async/await version

async function foo() {
  const res = await Promise.resolve(1);
  console.log(res); / / returns 1
}
Copy the code

The problem is that the example here has only one yield, but in practice we have multiple yields, as follows:

function co(gen) {
  gen = gen();
  const result = gen.next(); {done: false, value: Promise}

  return result.value.then((res) = > {
    // Execute the Promise function
    return gen.next(res); The next(param) method of the generator supports passing an argument, and returns the value of the argument to the variable declared before yield. Const res = yield promise.resolve ('generator') in foo above; The res is used to receive the param value in next(param)
  });
}

function* foo() {
  const res1 = yield Promise.resolve(1);
  console.log(res1);

  const res2 = yield Promise.resolve(2);
  console.log(res2);
}

// Execute the co function

co(foo);
Copy the code

After executing the above code, we find in the console that only 1 is printed. Why is this? Since our co function only executes result.then once, and our test case has two promises, how can we solve this problem? Recursively. Let’s improve the CO function

function co(gen) {
  gen = gen();

  // encapsulate the dep function to make recursive calls
  function dep(data) {
    const result = gen.next(data); {done: false, value: Promise}

    if (result.done) {
      // Terminating condition: no yield exits to avoid an infinite loop
      return;
    }

    return result.value.then((res) = > {
      // Execute the Promise function
      return dep(res); The next(param) method of the generator supports passing an argument, and returns the value of the argument to the variable declared before yield. Const res = yield promise.resolve ('generator') in foo above; The res is used to receive the param value in next(param)
    });
  }

  dep();
}

function* foo() {
  const res1 = yield Promise.resolve(1);
  console.log(res1);

  const res2 = yield Promise.resolve(2);
  console.log(res2);
}

// Execute the co function

co(foo);
Copy the code

After executing the improved CO function, we find that we can see 1 and 2 on the console. At this point, a simplified version of the CO function is achieved, of course, the complete CO function processing details are also a lot of, but the basic core code we even realized the completion, there are shortcomings, welcome to point out in the comments.