Generator is a new syntax for ES6. Generator is implemented through coroutines and has the ability to pause and surrender execution, and then resume execution from the pause point. This allows the Generator to handle asynchronous logic. However, Generator has no self-executing function, so it is usually used with actuators such as CO.

Shortly after Generator was released async/await was released, which is equivalent to a Generator with its own executor, and the syntax is relatively more semantic, so it is more common than Generator.

This section explores how to implement a Generaor self-executor without async/await to achieve similar effects as async/await.

Take the following code for example:

function* foo() {
  const a = yield 1;
  const b = yield 2;
  const c = yield 3;
  return 4;
}
Copy the code

For synchronized code, it’s very simple to make it run automatically:

function run(fn) {
  const it = fn();
  let result = it.next();
  while(! result.done) { result = it.next(result.value); }return result.value;
}

const result = run(foo);
console.log(result); / / [1, 2, 3]
Copy the code

But this simple approach does not handle asynchronous logic. To handle asynchronous logic, we need:

  1. The self-executing function needs to return a Promise object to get the Generator’s final result
  2. Each callnextAfter that, the returned result is converted into a Promise object, and after the Promise is completed, thethenMethod to continuenextMethod until the Generator completes

With this in mind, let’s start with some easy-to-understand code:

function mockFetch(data, timeout, fail) {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
      The fail parameter is used to simulate an asynchronous request error
      if (fail) {
        reject("error");
      }

      resolve(data);
    }, timeout);
  });
}

function* bar() {
  const a = yield mockFetch("a".100);
  const b = yield mockFetch("b".100);
  const c = yield mockFetch("c".100);
  return [a, b, c];
}

const it = bar();
const res1 = it.next();

res1.value.then((a) = > {
  const res2 = it.next(a);
  res2.value.then((b) = > {
    const res3 = it.next(b);
    res3.value.then((c) = > {
      const res4 = it.next(c);
      console.log("result", res4); // result { value: [ 'a', 'b', 'c' ], done: true }
    });
  });
});
Copy the code

With the above code, you have implemented a basic self-executor, but this approach only applies to bar functions.

Let’s actually implement the self-executing functions that satisfy our needs.

Start by creating a run function that takes a Generator function and its parameters as arguments and returns a Promise object:

function run(fn, ... args) {
  return new Promise((resolve, reject) = >{}); }Copy the code

Before executing the Generator function, we need to make some boundary judgments:

function run(fn, ... args) {
  return new Promise((resolve, reject) = > {
    let it;
    // fn must be a function
    if (typeof fn === "function") { it = fn(... args); }The object returned by fn must be an iterator
    if(! it ||typeofit.next ! = ="function") {
      returnresolve(it); }}); }Copy the code

Now comes the big story:

function run(fn, ... args) {
  return new Promise((resolve, reject) = > {
  	/ *... * /
    onResolved();

    function onResolved(value) {
      let res = it.next(value);
      next(res);
    }

    function next(res) {
      if (res.done) {
        return resolve(res.value);
      }
      // Convert the result to a Promise, and then do the unified processing
      Promise.resolve(res.value).then(onResolved); }}); }Copy the code

Now calling the bar function is fairly simple:

run(bar).then((data) = > {
  console.log(data); / / [1, 2, 3]
});
Copy the code

Next comes error handling for asynchronous operations. In async/await, when an asynchronous operation fails, try/catch can be used to catch and handle the error:

async function baz() {
  try {
    const a = await mockFetch("a".100.true);
  } catch (error) {
    // Error handling}}Copy the code

To pass a Promise Reject exception to a Generator, use the throw method:

function run(fn, ... args) {
  return new Promise((resolve, reject) = > {
 		/ *... * /
    onResolved();

    function onResolved(value) {
      let res;
			// If an exception is not caught inside the Generator, it will be thrown out, so external exception handling is required
      try {
        res = it.next(value);
      } catch (error) {
        return reject(error);
      }

      next(res);
    }

    function onRejected(reason) {
      let res;

      try {
        // Pass the reason of reject to the Generator
        res = it.throw(reason);
      } catch (error) {
        return reject(error);
      }

      next(res);
    }

    function next(res) {
      if (res.done) {
        return resolve(res.value);
      }
      // Convert the result to a Promise, and then do the unified processing
      Promise.resolve(res.value).then(onResolved, onRejected); }}); }Copy the code

Write some code to test it:

// There is no internal error handling
function* foo() {
  const a = yield mockFetch("data".1000."error");
}

// There is an internal error handling
function* bar() {
  const a = yield mockFetch("data".1000);
  let b;
  try {
    b = yield mockFetch("data".1000."error");
  } catch (error) {
    console.log("error happened");
  }
  return [a, b];
}

run(foo).catch((error) = > {
  console.log("foo error", error); // foo error error
});

run(bar).then((data) = > {
  console.log("bar data", data); // bar data ["data", undefined]
});
Copy the code

Here we have a simplified version of CO, complete with the following code:

function run(fn, ... args) {
  return new Promise((resolve, reject) = > {
    let it;
    // fn must be a function
    if (typeof fn === "function") { it = fn(... args); }The object returned by fn must be an iterator
    if(! it ||typeofit.next ! = ="function") {
      return resolve(it);
    }

    onResolved();

    function onResolved(value) {
      let res;

      try {
        res = it.next(value);
      } catch (error) {
        return reject(error);
      }

      next(res);
    }

    function onRejected(reason) {
      let res;

      try {
        res = it.throw(reason);
      } catch (error) {
        return reject(error);
      }

      next(res);
    }

    function next(res) {
      if (res.done) {
        return resolve(res.value);
      }
      // Convert the result to a Promise, and then do the unified processing
      Promise.resolve(res.value).then(onResolved, onRejected); }}); }Copy the code

If you have any comments or suggestions on this article, welcome to discuss and correct!