Although it is now possible to use async functions instead of Generator actuators, it is important to understand how Generator actuators work.

If you don’t know about Generator then you need to look here.

The examples run on Console (Google version 76.0.3809.100) and are written with JavaScript features supported by the latest browsers, regardless of compatibility.

The term to distinguish

Generator is an object returned after the Generator function has run.

Principle of actuator

The principle of a

Having the features of a Generator next function, the next function returns the following structure:

{ value: 'world', done: false }
Copy the code

or

{ value: undefined, done: true }
Copy the code

We can run the next function recursively, and stop the next function if done is true.

Principle 2

The yield expression itself returns no value, or always returns undefined.

The next method can take an argument that is treated as the return value of the previous yield expression.

Because the argument to the next method represents the return value of the previous yield expression, passing the argument is invalid the first time the next method is used.

function* test() {
  const a = yield "a"
  console.log(a)
  return true
}
const gen = test()
Next () returns {value: "a", done: false}
// The argument to the first next() function is invalid and does not need to be passed
const nextOne = gen.next() 
// 传递参数 nextOne.value
// Console. log outputs 'a' in the test function
// Next () returns {value: true, done: true}
gen.next(nextOne.value)
Copy the code

The principle of three

Gen. throw(exception) can throw an exception and resume execution of the generator (assuming a try catch yield statement), returning an object with both done and value attributes. And the exception can be passed through the try… Catch block to catch. We can throw a Promise error with gen.throw, and then use a try catch to intercept the Promise error.

function* gen() {
  let a
  try {
    a = yield 42
  } catch (e) {
    console.log('Error caught! ')}console.log(a)
  yield 33
}

var g = gen()
g.next() // { value: 42, done: false }
g.throw(new Error('Something went wrong')) // "Error caught!"
Copy the code

Simple actuators

The simple executable code is as follows:

/** generator executor * @param {Function} func generator Function * @return {Promise} returns a Promise object */
function generatorExecuter(func) {
  const gen = func()

  function recursion(prevValue) {
    const next = gen.next(prevValue)
    const value = next.value
    const done = next.done
    if (done) {
      return Promise.resolve(value)
    } else {
      return recursion(value)
    }
  }
  return recursion()
}
Copy the code

The code is not complicated, and the simplest executor comes out. If you’ve been through the article step by step and understood the principles, the code should be easy to understand.

Consider that the type of yield is Promise

The above code is incorrect for executing the following Generator function:

function* test() {
  const a = yield Promise.resolve('a')
  console.log(a)
  return true
}
generatorExecuter(test)
Copy the code

After running the code above, the test function console.log outputs not a, but a Promise {

: “A “}.

So our code needs to do this:

GeneratorFunc generator * @param {GeneratorFunction} generatorFunc generator * @return {Promise} returns a Promise object */
function generatorExecuter(generatorFunc) {
  return new Promise((resolve) = > {
    const generator = generatorFunc()
    // Triggers the first execution of the next function defined below
    onFullfilled()
    
    /** * The callback that Promise successfully handles * the callback will execute generator.next() * @param {Any} value yield expression returns the value */
    function onFullfilled(value) {
      let result
      // Next () executes the generator body code step by step,
      result = generator.next(value)
      next(result)
    }
    
    function next(result) {
      const value = result.value
      const done = result.done

      if (done) {
        return resolve(value)
      } else {
        return Promise.resolve(value).then(onFullfilled)
      }
    }
  })
}
Copy the code

GeneratorExecuter (test) should be run again.

Consider a try catch to catch yield errors

I’m using principle three, so if you don’t know, go back.

We cannot yield a try catch error using the above executor:

function* test() {
  try {
    const a = yield Promise.reject('error')}catch (err) {
    console.log('发生错误了:', err)
  }
  return true
}
generatorExecuter(test)
Copy the code

Uncaught (in Promise) error is reported after the above code is executed, instead of: error.

To change it to something like this (use Gen. throw) :

GeneratorFunc generator * @param {GeneratorFunction} generatorFunc generator * @return {Promise} returns a Promise object */
function generatorExecuter(generatorFunc) {
  return new Promise((resolve, reject) = > {
    const generator = generatorFunc()
    // Triggers the first execution of the next function defined below
    onFullfilled()
    /** * The callback that Promise successfully handles * the callback will execute generator.next() * @param {Any} value yield expression returns the value */
    function onFullfilled(value) {
      let result
      // Next () executes the generator body code step by step,
      // If an error is reported, we need to intercept, reject
      try {
        // The yield expression itself returns no value, or always returns undefined.
        The // generator.next method can take an argument that is treated as the return value of the previous yield expression.
        // Since the argument to the generator.next method represents the return value of the previous yield expression,
        // So the first time you use the generator.next method, the passing argument is invalid.
        result = generator.next(value)
      } catch (error) {
        return reject(error)
      }
      next(result)
    }
    /** * The callback will execute generator.throw() so that a try catch can intercept the yield XXX error * @param {Any} reason the failure reason */
    function onRejected(reason) {
      let result
      try {
        // A try catch is used to catch code that fails when the recovery generator executes
        // The gen.throw() method is used to throw an exception to the generator and resume its execution,
        // Return an object with both done and value attributes.
        result = generator.throw(reason)
      } catch (error) {
        return reject(error)
      }
      // If the error from gen.throw() is caught, execution can continue, otherwise subsequent runs are terminated
      // This can be synchronized
      // (not a try catch, but a try catch yield statement)
      next(result)
    }

    function next(result) {
      const value = result.value
      const done = result.done

      if (done) {
        return resolve(value)
      } else {
        return Promise.resolve(value).then(onFullfilled, onRejected)
      }
    }
  })
}
Copy the code

Consider other types of yield

Consider other types of yield, such as generator functions, which can be easily handled by making promises.

The above code is incorrect for executing the following Generator function:

function* aFun() {
  return 'a'
}

function* test() {
  const a = yield aFun
  console.log(a)
  return true
}
generatorExecuter(test)
Copy the code

After running the above code, the test function console.log outputs not a, but the following string:

ƒ *aFun() {
  return 'a'
}
Copy the code

So our code needs to do this:

/ * * * * @ the generator actuators param {GeneratorFunction | generator} generatorFunc generator functions or the generator * @ return {Promise} Return a Promise object */
function generatorExecuter(generatorFunc) {
  if(! isGernerator(generatorFunc) && ! isGerneratorFunction(generatorFunc)) {throw new TypeError(
      'Expected the generatorFunc to be a GeneratorFunction or a Generator.')}let generator = generatorFunc
  if (isGerneratorFunction(generatorFunc)) {
    generator = generatorFunc()
  }

  return new Promise((resolve, reject) = > {
    // Triggers the first execution of the next function defined below
    onFullfilled()
    /** * The callback that Promise successfully handles * the callback will execute generator.next() * @param {Any} value yield expression returns the value */
    function onFullfilled(value) {
      let result
      // Next () executes the generator body code step by step,
      // If an error is reported, we need to intercept, reject
      try {
        // The yield expression itself returns no value, or always returns undefined.
        The // generator.next method can take an argument that is treated as the return value of the previous yield expression.
        // Since the argument to the generator.next method represents the return value of the previous yield expression,
        // So the first time you use the generator.next method, the passing argument is invalid.
        result = generator.next(value)
      } catch (error) {
        return reject(error)
      }
      next(result)
    }
    /** * The callback will execute generator.throw() so that a try catch can intercept the yield XXX error * @param {Any} reason the failure reason */
    function onRejected(reason) {
      let result
      try {
        // A try catch is used to catch code that fails when the recovery generator executes
        // The gen.throw() method is used to throw an exception to the generator and resume its execution,
        // Return an object with both done and value attributes.
        result = generator.throw(reason)
      } catch (error) {
        return reject(error)
      }
      // If the error from gen.throw() is caught, execution can continue, otherwise subsequent runs are terminated
      // This can be synchronized
      // (not a try catch, but a try catch yield statement)
      next(result)
    }

    function next(result) {
      const value = toPromise(result.value)
      const done = result.done

      if (done) {
        return resolve(value)
      } else {
        return value.then(onFullfilled, onRejected)
      }
    }
  })
}

/** * Consider other types of yield, such as generator functions, which can be easily handled by matching these types to promises
function toPromise(value) {
  if (isGerneratorFunction(value) || isGernerator(value)) {
    GeneratorExecuter returns Promise
    return generatorExecuter(value)
  } else {
    // Strings, objects, arrays, promises, and so on are converted to promises
    return Promise.resolve(value)
  }
}

/** * is a generator function */
function isGerneratorFunction(target) {
  if (
    Object.prototype.toString.apply(target) === '[object GeneratorFunction]'
  ) {
    return true
  } else {
    return false}}/** * is generator */
function isGernerator(target) {
  if (Object.prototype.toString.apply(target) === '[object Generator]') {
    return true
  } else {
    return false}}Copy the code

GeneratorExecuter (test) should be run again.

The final version

Generator actuators are not as difficult as you might think and take a little time to figure out.

/ * * * * @ the generator actuators param {GeneratorFunction | generator} generatorFunc generator functions or the generator * @ return {Promise} Return a Promise object */
function generatorExecuter(generatorFunc) {
  if(! isGernerator(generatorFunc) && ! isGerneratorFunction(generatorFunc)) {throw new TypeError(
      'Expected the generatorFunc to be a GeneratorFunction or a Generator.')}let generator = generatorFunc
  if (isGerneratorFunction(generatorFunc)) {
    generator = generatorFunc()
  }

  return new Promise((resolve, reject) = > {
    // Triggers the first execution of the next function defined below
    onFullfilled()
    /** * The callback that Promise successfully handles * the callback will execute generator.next() * @param {Any} value yield expression returns the value */
    function onFullfilled(value) {
      let result
      // Next () executes the generator body code step by step,
      // If an error is reported, we need to intercept, reject
      try {
        // The yield expression itself returns no value, or always returns undefined.
        The // generator.next method can take an argument that is treated as the return value of the previous yield expression.
        // Since the argument to the generator.next method represents the return value of the previous yield expression,
        // So the first time you use the generator.next method, the passing argument is invalid.
        result = generator.next(value)
      } catch (error) {
        return reject(error)
      }
      next(result)
    }
    /** * The callback will execute generator.throw() so that a try catch can intercept the yield XXX error * @param {Any} reason the failure reason */
    function onRejected(reason) {
      let result
      try {
        // A try catch is used to catch code that fails when the recovery generator executes
        // The gen.throw() method is used to throw an exception to the generator and resume its execution,
        // Return an object with both done and value attributes.
        result = generator.throw(reason)
      } catch (error) {
        return reject(error)
      }
      // If the error from gen.throw() is caught, execution can continue, otherwise subsequent runs are terminated
      // This can be synchronized
      // (not a try catch, but a try catch yield statement)
      next(result)
    }

    function next(result) {
      const value = toPromise(result.value)
      const done = result.done

      if (done) {
        return resolve(value)
      } else {
        return value.then(onFullfilled, onRejected)
      }
    }
  })
}

/** * Consider other types of yield, such as generator functions, which can be easily handled by matching these types to promises
function toPromise(value) {
  if (isGerneratorFunction(value) || isGernerator(value)) {
    GeneratorExecuter returns Promise
    return generatorExecuter(value)
  } else {
    // Strings, objects, arrays, promises, and so on are converted to promises
    return Promise.resolve(value)
  }
}

/** * is a generator function */
function isGerneratorFunction(target) {
  if (
    Object.prototype.toString.apply(target) === '[object GeneratorFunction]'
  ) {
    return true
  } else {
    return false}}/** * is generator */
function isGernerator(target) {
  if (Object.prototype.toString.apply(target) === '[object Generator]') {
    return true
  } else {
    return false}}Copy the code

Run the following example directly from Google Console:

/ / case
function* one() {
  return 'one'
}

function* two() {
  return yield 'two'
}

function* three() {
  return Promise.resolve('three')}function* four() {
  return yield Promise.resolve('four')}function* five() {
  const a = yield new Promise(resolve= > {
    setTimeout((a)= > {
      resolve('waiting five over')},1000)})console.log(a)
  return 'five'
}

function* err() {
  const a = 2
  a = 3
  return a
}

function* all() {
  const a = yield one()
  console.log(a)
  const b = yield two()
  console.log(b)
  const c = yield three()
  console.log(c)
  const d = yield four()
  console.log(d)
  const e = yield five()
  console.log(e)
  try {
    yield err()
  } catch (err) {
    console.log('Error occurred', err)
  }
  return 'over'
}

generatorExecuter(all).then(value= > {
  console.log(value)
})

/ / or
// generatorExecuter(all()).then(value => {
// console.log(value)
// })
Copy the code