Today we are talking about the new syntax “async + await” introduced in ES7, which is called the perfect solution for asynchrony. Of course it’s not like to start off by saying let’s make a perfect asynchronous solution and then call async, await.

There is a process.

Their predecessors, derived from a combination of generator + CO, implemented an optimal solution to an asynchronous solution that did not rely solely on callback functions and promises. I’m going to synchronize asynchronous code.

The generator function

A generator is literally called a generator. What does a generator do? It generates iterators. What’s the iterator?

Iterator: A solution proposed in ES6 to unify iterated data structures.

Let’s use code to explain the above concepts more clearly.

function *generator() {
  yield 1;
  yield 2;
  yield 3;
}
Copy the code

Such a function is called a generator function, but the structure of this function seems to be different from that of our normal function, and there are two flags in it that we haven’t used before in functions

  • A *
  • A yield keyword

I’m going to declare a function, not a normal function, called a generator function.

The yield keyword must be used with generator functions, not alone.

The basic use

Now let’s see how this function works, and what’s different from our normal function.

function *generator() {
  yield 1;
  yield 2;
  yield 3;
}

letit = generator(); // It is an iterator. console.log(it.next()); // {value: 1,done: false}
console.log(it.next()); // {value: 2, done: false}
console.log(it.next()); // {value: 3, done: false}
console.log(it.next()); // {value: undefined, done: true}
console.log(it.next()); // {value: undefined, done: true}
Copy the code

Execution Result:

The iterator function, which can call the next function an infinite number of times, outputs the values following yield.

The first three times NEXT outputs the corresponding three yields, and the next two times it returns undefined. Done indicates whether the iteration is complete. The iterator itself is a linked list-like structure that can be moved backwards by next and read from. Until the last {value: undefined, done:true}.

A step

In fact, we can see from the result that the code in the iterator can be executed in fragments.

Next write a rewrite based on the example above;

function *generator() {
  console.log("First time next")
  yield 1;
  console.log("The second next")
  yield 2;
  console.log("The third time next")
  yield 3;
}

letit = generator(); // It is an iterator. console.log("start")
console.log(it.next());
console.log("1")
console.log(it.next());
console.log("2")
console.log(it.next());
console.log("3")
Copy the code

The order of execution is

Yield a return value

Next, the yield return value.

function *generator() {
  let a = yield 1;
  console.log(The value of "a:" + a )
  let b = yield 2;
  console.log("The value of b:" + b )
  yield 3;
}

letit = generator(); // It is an iterator. console.log("First execution",it.next(10));
console.log("Second execution",it.next(11));
console.log("Third execution",it.next(22));
Copy the code

The value passed in the first next is meaningless. Let’s start with the second one. Each call to Next passes in the value as the return value from the previous Yeild.

That way, we can pass in the value we’re going to operate on outside.

Example: Read the contents of the file as the name of the next time

Next, there is a requirement. We need to read the contents of both folders in succession. As the name of the file to be read next time. This is implemented by a generator function.

The file directories are as follows:

const fs = require("fs").promises;
function* read() {
  let value = yield fs.readFile("a.txt"."utf-8");
  let value2 = yield fs.readFile(value, "utf-8");
  let value3 = yield fs.readFile(value2, "utf-8");
  return value3;
}

let it = read(a);let { done, value } = it.next();
value.then(data => {
  console.log(data, "1");
  let { done, value } = it.next(data);
  value.then(data => {
    console.log(data, "2");
    let { done, value } = it.next(data);
    value.then(data => {
      console.log(data, "3");
      let { done, value } = it.next(data);
      console.log(value);
    });
  });
});
Copy the code

Execution Result:

Co library

In fact, TJ wrote a library Co to solve our above problem.

const fs = require("fs").promises;
const co = require("co"); // Using Generator functions, read from two files and write to a third file. The file name can be passed in by itself, for example, read a.txt and b.txt and write to c.txt. cofunction* read() {
  let value = yield fs.readFile("a.txt"."utf-8");
  let value2 = yield fs.readFile(value, "utf-8");
  let value3 = yield fs.readFile(value2, "utf-8");
  return value3;
}
co(read()).then(data => {
  console.log(data);
});
Copy the code

Output result:

Simulation Co library

Now let’s figure out how to write the CO function, and let’s simulate it.

const fs = require("fs").promises;
// const co = require("co"// Use Generator functions to read from two files and write to a third file, the file name can be passed in by itself, for example, read a.txt and b.txt and write to c.txt; cofunction* read() {
  let value = yield fs.readFile("a.txt"."utf-8");
  let value2 = yield fs.readFile(value, "utf-8");
  let value3 = yield fs.readFile(value2, "utf-8");
  return value3;
}
function co(it) {
  return new Promise((resolve, reject) => {
    functionNext (data) {// For the first time next can not pass values.let { done, value } = it.next(data);
      if (done) {
        resolve(value);
      } else {
        Promise.resolve(value).then(data => {
          next(data);
        });
      }
    }
    next();
  });
}
co(read()).then(data => {
  console.log(data);
});
Copy the code

When we implement Co, the result is the same as above. Of course, here is a simple version of CO, if you want to know more about CO, you can go to Github to study the code is not much more than a few hundred lines. This is a CO + Generator model.

async + await

So we’re going to extend async and await on top of that and let’s see what that looks like.

const fs = require("fs").promises;
// const co = require("co"// Use Generator functions to read from two files and write to a third file, the file name can be passed in by itself, for example, read a.txt and b.txt and write to c.txt; co asyncfunction read() {
  let value = await fs.readFile("a.txt"."utf-8");
  let value2 = await fs.readFile(value, "utf-8");
  let value3 = await fs.readFile(value2, "utf-8");
  return value3;
}

read().then(data => {
  console.log(data);
});
Copy the code

We changed the * to async; Change yield to await. Again, the return of a function declared via async is still a Promise. And for await it must be used with async.