JavaScript supports Generator functions and Objects similar to Python Generators.
Let’s see what is JavaScript Generators and how it works.

What is Generator
  • Generator in ES6 is a new way of working with functions and iterators.
  • It appears to be a function but behaves like an iterator which simplifies the task of writing iterators.
  • It is different from a normal function in a way that it can stop midway and then continue from where it stopped.
  • In layman’s terms, It is similar to the way we put marker/bookmark to a page of the book we read. Then later we resume reading the book from that particular page instead of start reading all over again. Here we acted as a generator function.
  • It can generate a series of results instead of a single value.
Generator Syntax
  • Creating a generator is similar to normal function with some differences that it requires a * between function keyword and function name.
  • It allows any number of spaces or no spaces in between.
  • It uses a yield keyword to pause and resume the execution.
  • Since it is just a function, we can use it anywhere like normal function i.e. inside objects and class methods.
  • Basic syntax:

    // All below formats are valid with spaces/no spaces in between function keyword, * and function name.
    function * functionName() {
        // Your generator related codes here.
    }
    
    // OR below format.
    function *functionName() {
        // Your generator related codes here.
    }
    
    // OR below format.
    function* functionName() {
        // Your generator related codes here.
    }
    
    // OR below format.
    function  *  functionName() {
        // Your generator related codes here.
    }
    
    // OR below format.
    
    function*functionName() {
        // Your generator related codes here.
    }
    
    // Inside Class method.
    class testClass {
        *functionName() {}
    }
    
    // Inside object
    var testObject = {
        *functionName() {}
    }
Yield in Generator
  • yield keyword is used to return the data and pause the execution of the generator.
  • Every time generator encounters a yield, it halts the execution at that point and returns the value specified.
    Then resumes the execution on calling the next() method on Generator object until it finds the next yield.
  • In the context of Generator, we do not say that it returned a value. We say that it has yielded the value.
  • Using return inside a Generator will set done property to true after which it cannot generate any more values.
    All the codes after the return statement will not execute.
  • Syntax:

    // Generator with yield. 
    function * generator() {
        console.log('First execution.');
        yield 'Yield First execution, saved the state and exit';
        console.log('Second execution will be on next() call.');  
        yield 'Yield second execution, saved the state and exit';
    }
    
    const generatorObj = generator();
next() in generator
  • Generator returns an object on which we can call next().
  • Every invocation of next() will return an object containing returned value and status of generator execution.
  • value property will contain the value and done property will be true or false mentioning generator’s execution status.
  • When done becomes true, generator stops and won’t generate any more values.
    {
        value: Any,
        done: true|false
    }
  • Sample code: ( jsfiddle link: generator example )
    function * generator() {
        console.log('First execution.');
        yield 'Yield First execution, saved the state and exit';
        console.log('Second execution will be on next() call.');  
        yield 'Yield second execution, saved the state and exit';
    }
    
    const generatorObj = generator();
    
    // After below execution, generator will yield value, return below object and pause execution.
    // {value: 'Yield First execution, saved the state and exit', done: false}
    setTimeout(() => console.log(generatorObj.next()), 1000);
    
    // After below execution, generator will yield value, return below object and pause execution.
    // {value: 'Yield First execution, saved the state and exit', done: false} 
    setTimeout(() => console.log(generatorObj.next()), 3000);
    
    // After below execution, generator will yield value, return below object and close execution.
    // {value: undefined, done: true}
    setTimeout(() => console.log(generatorObj.next()), 5000);
    
  • Below example shows the different state of a generator during its execution of how it gets suspended when it is yielded, resumes on next() call and then gets closed after all execution:

  • If we use return then it will not execute any code present after the return statement.
    Sample code: ( jsfiddle link: generator with return statement )

    function * generator() {
        console.log('First execution.');
        yield 'Yield First execution, saved the state and exit';
        return 'Execution ends here due to return and will not execute rest of the codes.';  
        // All below codes will not get executed.
        yield 'Yield second execution, saved the state and exit';
    }
    
    const generatorObj = generator();
    
    // After below execution, generator will yield value, return below object and pause execution.
    // {value: 'Yield First execution, saved the state and exit', done: false}
    setTimeout(() => console.log(generatorObj.next()), 1000);
    
    // After below execution, generator will yield value, return below object and completes the execution.
    // {value: 'Execution ends here due to return and will not execute rest of the codes.', done: true}
    setTimeout(() => console.log(generatorObj.next()), 3000);
    
    // generator execution is already completed so it will just return value as undefined and done as true.
    // {value: undefined, done: true}
    setTimeout(() => console.log(generatorObj.next()), 5000);
    
  • We can also pass values to next().
    Sample code: ( jsfiddle linknext() with value passed )

    function * generator() {
        console.log('First execution.');
        yield 'Yield First execution, saved the state and exit';
        console.log('Second execution will be on next() call.');  
        let passedArgument = yield 'Yield second execution, saved the state and exit';
        yield passedArgument;
    }
    
    const generatorObj = generator();
    
    // After below execution, generator will yield value, return below object and pause execution.
    // {value: 'Yield First execution, saved the state and exit', done: false}
    setTimeout(() => console.log(generatorObj.next()), 1000);
    
    // After below execution, generator will yield value, return below object and pause execution.
    // {value: 'Yield First execution, saved the state and exit', done: false} 
    setTimeout(() => console.log(generatorObj.next()), 3000);
    
    // After below execution, generator will yield value, return below object and pause execution.
    // {value: "This is the passed value", done: false}
    setTimeout(() => console.log(generatorObj.next('This is the passed value')), 5000);
    
    // After below execution, generator will yield value, return below object and close execution.
    // {value: undefined, done: true}
    setTimeout(() => console.log(generatorObj.next()), 7000);
Why use Generator
  • Implementing Iterables:
    • Implementation of an iterator in JavaScript requires things to do manually like:
      • Manually create an iterator object.
      • Manually implement next() method.
      • Manually make return object of next() in the format:  { value: Any, done: true|false }
      • Manually save the state.
    • Below sample code shows how hard it is to manually implement everything in an iterator.

      const iterableObj = {
          [Symbol.iterator]() {
              var counter = 0;
              return {
                  next() {
                      counter++;
                          if (counter === 1) {
                              return { value: 'step one', done: false};
                          } else if (counter === 2) {
                              return { value: 'step two', done: false};
                          } else if (counter === 3) {
                              return { value: 'step three', done: false};
                          }
                      return { value: '', done: true };
                  }
              }
          },
      }
      
      for (const val of iterableObj) {
          console.log(val);
      }
      
    • Below sample code shows how easy it is with Generator which handles most of the things on its own.
      function * iterableObj() {
          yield 'step one';
          yield 'step two';
          yield 'step three';
      }
      
      for (const val of iterableObj()) {
        console.log(val);
      }
  • Lazy execution:
    Generator helps to delay the evaluation of an expression until its value is required.
    So If we don’t need a value, it won’t exist and is calculated only we need it.
  • Memory efficient:
    It is memory efficient because we generate only the values that are required.
    We need to pre-generate all the values in case of normal functions and keep them in case we use them later but generators can defer the computation of any expressions/codes until we need it.
Limitations
  • Generators are one-time access only. Once you’ve exhausted all the values, you can’t iterate over it again.
    To generate the values again, you need to make a new generator object.
  • Generators do not allow random access as possible with arrays. Since the values are generated one by one, accessing a random value would lead to computation of values till that element. Hence, it’s not random access.

 

I hope you will get some idea of JavaScript Generator and its basic functionalities from this post.

Happy coding!!!

View at Medium.com

Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *