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 link: next() 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); }
- Implementation of an iterator in JavaScript requires things to do manually like:
- 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!!!