“Iteration, like friction, is likely to generate heat instead of progress.”
― George Eliot
Introduction
I’m very sure we are all familiar with a function which returns a single value, whether it is a string, number, boolean, object, etc. In JavaScript there is another type of function which can return (yield) multiple values on-demand and it allows us to create data streams with ease. These functions are generator functions.
What are Generator Functions?
I believe learning by reading examples is faster than theories. So, here is an example of a generator function:
function* generatorFunction(){
yield '1st output!';
yield '2st output!';
return 'Last Output!';
}
First, note that there is an asterisk (*
) symbol at the end of function
. That is how to declare a generator function. Second, there are two yield
s in the function. Put it simply, yield
is like return
, it returns a value but it does not finish the function’s execution yet.
The diagram below shows how it works:

Or, see the snippet below for better explanation by code:
function* generatorFunction(){
yield '1st output!';
// ------------------- pauses on 1st call
yield '2st output!';
// ------------------- pauses on 2nd call
return 'Last Output!';
// ------------------- ends on 3rd call
}
A generator function can be called multiple times and gives different defined results, or even infinite results. It depends on the number of yield
s.
Next question, how do we get the outputs from a generator function? See the snippet below:
const gen = generatorFunction();
console.log(gen.next());
/* { done: false, value: "1st output!" } */
console.log(gen.next());
/* { done: false, value: "2st output!" } */
console.log(gen.next());
/* { done: true, value: "Last Output!" } */
console.log(gen.next());
/* { done: true, value: undefined } */
First, we create the generator object and assign it to the variable gen
. Then, we get the result by calling gen.next()
. The result is an object, gives the information whether the generator function is done or not, and of course the value
of the yield output.
To get the next yield output, call .next()
again. If the done
property is true
, it means that the generator function is finished. Another attempt of calling .next()
will give value
a result of undefined
.
Usage
I’m sure the concept is quite easy to understand, but how can we implement generator functions in our code? What is it for?
Implement Iterables
When we want to implement iterators, we need to make an iterator object with .next()
. And we also need to save the state manually. Imagine how complex it would be if your code is full of these. See this example below:
const iterableObject = {
[Symbol.iterator]: () => {
let counter = 0;
return {
next: () => {
counter++;
switch(counter){
case 1: return { value: 'One', done: false };
case 2: return { value: 'Two', done: false };
case 3: return { value: 'Three', done: false };
}
return { value: 'No more.', done: true };
}
}
},
}
for (const val of iterableObject) {
console.log(val);
}
// One
// Two
// Three
Rather long and complicated, isn’t it? And here’s an implementation using generators, giving the same result, simpler, and more declarative:
function* iterableObject() {
yield 'One';
yield 'Two';
yield 'Three'
}
for (const val of iterableObject()) {
console.log(val);
}
// One
// Two
// Three
Infinite Data Streams
We can create an iterator which never ends. See this example below:
function* oddNumbers() {
let num = 1;
while (true) {
yield num;
num = num + 2;
}
}
const odd = oddNumbers();
console.log(odd.next().value); // 1
console.log(odd.next().value); // 3
console.log(odd.next().value); // 5
First, we create a generator function which has an infinite while
loop. Inside the loop, we put a yield
for the current value to be returned. Then, we calculate the next value to be returned: num = num + 2;
. When we call next()
again, we will get the next odd number. This continues as long as we want.
As Observers
Generators can become observers by giving values as arguments on the next()
function. Here is a simple example:
function* observer() {
while (true) {
const input = yield;
console.log(input);
}
}
const obs = observer();
obs.next('a');
obs.next('b');
obs.next('c');
obs.next('d');
// b
// c
// d
First, we create a generator function with the same pattern as the infinite data streams section you read before. Second, see the statement: const input = yield;
. This statement enables yield
to get the value from the argument sent from next()
. Third, it should be known that the argument of the first next()
will always be ignored, because it is used to start the generator. Finally, we can call as many next()
as we want.
This is just an example to understand the pattern. You can read more about the observer pattern with generator functions here.
Conclusion
Though it’s rarely used, generator functions are great for making iterable objects. A proper use could optimize our applications and simplify some codes we are working on.
Do you have any experience in implementing generator functions? What use case did you use it for? Let me know in the comments!
Live your code and code your life!
“Generate the belief in what you wish to achieve.”
― Steven Redhead, Life Is Simply A Game