JavaScript Generators are a special type of function which lets you suspend the function execution and then come back later and resume the execution.
We can define a generator function with the function
keyword followed by an asterisk (function*
). Currently, there is no such option available for arrow functions to create generator functions.
The generator function provides a powerful keyword called yield
which pauses the function execution and returns the yield
expression value.
function* generatorFn() { console.log('start'); yield 1; console.log('After yield 1'); yield 2; console.log('end'); }
The generator function doesn’t execute right away when we invoke it. Instead, it returns a Generator
object which contains three utility methods.
- next()
- return()
- throw()
The next() method
When we call the next()
method, the Generator function executes until the next yield
expression. This method returns an object containing a value
property that holds the yielded value and a done
property that holds a boolean value which indicates if the function has finished its execution or not.
function* generatorFn() { console.log('start'); yield 10; console.log('After yield 10'); console.log('X', yield 20); console.log('end'); } const gen = generatorFn(); console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); /* output: start { value: 10, done: false } After yield 10 { value: 20, done: false } X undefined end { value: undefined, done: true } */
First next()
call – function will execute till first yield
expression yield 10
. Therefore, “start” will be printed and an object will be returned by yield 10
containing properties value
as “10” and done
as false
.
Second next()
call – function will start the execution from the second line (without yield
expression) till the next yield
expression yield 20
. Remember when execution pauses on a line, it only executes the yield
expression and the rest of the statement will execute in the next next()
call. So, here on the second next()
call, only yield 20
will execute and pause, and the rest console statement will execute in the next iteration.
Third next()
call – function will start its execution from the fourth line, executing the console statement and replacing yield 20
with undefined
(as we passed no argument in next()
. we’ll learn more about it later). As there is no more yield
expression left, execution will continue till the end and return the object containing value
as undefined
and done
as true
.
Once the execution is finished, call to next()
will always return the object with properties value
as undefined
and done
as true
.
Placing return statement inside generator function
If we have a return
statement inside the generator function, then the return
statement will finish the execution and the returned object will have value
as the returned value and done
as true
.
function* generator() { yield 1; return 5; } const gen = generator(); console.log(gen.next()); // { value: 1, done: false } console.log(gen.next()); // { value: 5, done: true }
If we place the return
statement before any yield
expression, the return
statement will finish the execution and the yield
expression will be ignored.
function* generator() { yield 1; return 5; yield 10; } const gen = generator(); console.log(gen.next()); // { value: 1, done: false } console.log(gen.next()); // { value: 5, done: true } console.log(gen.next()); // { value: undefined, done: true }
Passing value in next() method
We can call the next()
method with an argument, which will replace the last executed yield
expression where the execution was paused with the argument value.
function* generator() { yield 10; const third = yield 20; yield third; } const gen = generator(); console.log(gen.next()); // { value: 10, done: false } console.log(gen.next(55)); // { value: 20, done: false } console.log(gen.next(99)); // { value: 99, done: false } console.log(gen.next()); // { value: undefined, done: true }
When the first next()
is called, yield 10
executed and execution was paused there.
When next(55)
is called, it replaced the last yield
expression i.e. yield 10
with “55” (We are not using this value here so we won’t see a difference), then executed yield 20
and execution paused there.
When next(99)
is called, it replaced the last yield
expression i.e. yield 20
with “99” and assigned value “99” to variable “third”. Then yield third
executed and returned object with properties value
as “99” and paused execution there.
Finally, when the last next()
is called, code after the last yield
executed, here there was no code available after the last yield
so it simply finished the execution and returned the object with done
as true
.
The return() method
When we call the return()
method, it will finish the function execution there itself where the execution was paused. It will return an object with properties value
as undefined
and done
as true
. We can also pass a value in the return()
method which will replace the value
property in the returned object.
function* generator() { console.log("start"); yield 10; console.log("after yield 10"); yield 20; console.log("end"); } const gen = generator(); console.log(gen.next()); console.log(gen.return(55)); console.log(gen.next()); /* output: start { value: 10, done: false } { value: 55, done: true } { value: undefined, done: true } */
The throw() method
This method basically stops the execution there itself and throws an exception. This method won’t return any object like next()
and return()
does. We can pass any error information to this method for debugging purposes.
function* generator() { console.log("start"); yield 10; console.log("after yield 10"); yield 20; console.log("end"); } const gen = generator(); console.log(gen.next()); console.log(gen.throw(new Error('Some error occured'))); console.log(gen.next()); /* output: start { value: 10, done: false } Uncaught Error: Some error occured */
Generator object as iterator
The generator object is iterable, so we can easily loop through it and use the values returned by each yield
expression.
function* generator() { yield 25; yield 45; yield 65; } // using for...of loop for (let value of generator()) { console.log(value); } // 25 // 45 // 65 // Array.from() console.log(Array.from(generator())); // [25, 45, 65] // spread operator console.log([...generator()]); // [25, 45, 65]
Using the yield* keyword with iterables
The yield*
keyword iterates over the iterable operand and yields each value returned by it. This keyword must be used with iterables only, else it will throw an error.
Some of the built-in iterables are Array, Map, String, Generators, etc.
Example with string and array
function* generator() { yield 10; yield [20, 30]; yield* [40, 50]; yield 'html'; yield* 'js'; } const gen = generator(); console.log(gen.next().value); // 10 console.log(gen.next().value); // [20, 30] console.log(gen.next().value); // 40 console.log(gen.next().value); // 50 console.log(gen.next().value); // html console.log(gen.next().value); // j console.log(gen.next().value); // s
Example with generator
// print a series of numbers like 1223334444...n(n times) function* repeat(n) { let count = 1; while (count <= n) { yield n; count++; } } function* generateSequence(n) { let count = 1; while (count <= n) { yield* repeat(count); count++; } } const gen = generateSequence(3); console.log(gen.next().value); // 1 console.log(gen.next().value); // 2 console.log(gen.next().value); // 2 console.log(gen.next().value); // 3 console.log(gen.next().value); // 3 console.log(gen.next().value); // 3 console.log(gen.next().value); // undefined
Multiple instances of generator function
We can create multiple instances of generators and they don’t interfere with each other’s execution and work independently. Let’s see one example.
function* generator() { yield 10; yield 20; } const gen1 = generator(); const gen2 = generator(); console.log(gen1.next()); // { value: 10, done: false } console.log(gen1.next()); // { value: 20, done: false } console.log(gen2.next()); // { value: 10, done: false } console.log(gen1.next()); // { value: undefined, done: true } console.log(gen2.next()); // { value: 20, done: false } console.log(gen2.next()); // { value: undefined, done: true }
You may also like
- JavaScript getters and setters
- Map in JavaScript and when its a better choice than Object
- JavaScript Set object to store unique values
Web Developer by Profession, Blogger by Passion.
JavaScript | React | Next | Angular | Vue | HTML | CSS