Introduction to Generators
Generators, basic introduction.
What was the feeling you got the first time you saw the asterisk (*) operator? Some saw it first in other languages other than Javascript. I first saw it in C/C++ and it is a pointer to a memory address. So when I saw it in Javascript and knowing that it wasn't a pointer, I felt that it was going to be a complicated concept to grasp the moment I saw Generator as the topic.
What is a generator?
A generator is an iterator and implements the .next() method. To get the iterator, we use a Generator function. In normal function calls without promises, the function runs from top to bottom, but a generator function breaks its run depending on the number of occurrences of the yield keyword. Let's start with the normal function execution:
function normalFunction() {
console.log(1);
console.log(2);
console.log(3);
}
normalFunction();
// the function prints 1, 2, 3 in that orderSo, such normal functions execute till the end and don't stop unless there's an error. But, a generator function works in a different way. Let's first see how it runs before explanations.
function* generatorFunc() {
yield 1;
console.log(1);
yield 2;
console.log(2);
yield 3;
console.log(3);
}
let generator = generatorFunc();
let yield1 = generator.next(); // { value: 1, done: false }
console.log(yield1);
let yield2 = generator.next(); // { value: 2, done: false }
console.log(yield2);
let yield3 = generator.next(); // { value: 3, done: false }
console.log(yield3);
/**
* The output of the code above is
* { value: 1, done: false }
* 1
* { value: 2, done: false }
* 2
* { value: 3, done: false }
* 3
*/This looks quite counter-intuitive, but that is how generators work. So, when the function was called, it immediately returns an iterator object, from which we can call the .next() method to get an object with the following interface:
interface INextValue {
value: any;
done: boolean;
}So when generator.next() is called, it runs until the next yield keyword and yields the value. It keeps yielding values until the last yield keyword and last line of the function, for which it returns
{ done: true }for every subsequent call. This way enables the use of generators for quite some complex things. Generators can be used in the implementation of iterables. Iterables were used to being implemented using the syntax:
{
[Symbol.iterator]: function() {
return {
next: function() {
return { value: any, done: Boolean };
}
};
}
}Since the [Symbol.iterator] method expects to return an iterator object with the .next() method, and given that a generator function returns an iterator object, we can then make the implementation of an iterable much easier:
{
[Symbol.iterator]: function*() {}
}Yes! It's that simple and straightforward. Generators also enable communication between a function and the outside. The yield can return arguments passed to the .next() method to the inside of the generator function. Let's see an example:
function* gen() {
let name = yield "What's your name?";
console.log(name);
}
let generator = gen();
let val = generator.next("My name is Chimezie");
// val will become { value: "What's your name?", done: false }
// while name will be "My name is Chimezie"So with this, we can easily pass values in and out of a function interactively. We can even make a generator that infinitely yields values. What we can do with generators are many, even in async iterators and the Streaming API. Thanks.