Hello guys, I will be talking about generators in this blog. Let's start with a simple introduction, then let's understand the actual use cases of generators.
Definition
Generators are a special type of function in JavaScript that allow you to control the flow of execution. Unlike regular functions that run to completion, generators can be paused in the middle of their execution and later resumed.
They are declared using the function*
keyword.
Code
A typical generator code in JavaScript looks like this:
function* exampleGenerator() {
yield 1;
yield 2;
yield 3;
}
const generator = exampleGenerator();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }
/* after this everytime you call console.log(generator.next()),
you will get { value: undefined, done: true }
*/
The idea is simple, for every time you call generator.next()
, the next value generated by the generator gets returned. And those values are generated by the yield
keyword.
Example Use Case
A very basic example use case of the generators would be to find the Fibonacci Sequence.
Fibonacci Sequence if you don't know is a sequence of numbers in which the current number is equal to sum of previous two numbers, and the first 2 numbers are 0 and 1.
For example:
0, 1, 1, 2, 3, 5, 8, 13...
Here's the code for the same using generator.
function* fibGenerator() {
let curr = 0;
let next = 1;
while(true) {
yield curr;
[curr, next] = [next, curr + next];
}
}
const fibonacci = fibGenerator();
console.log(fibonacci.next().value); //0
console.log(fibonacci.next().value); //1
console.log(fibonacci.next().value); //1
console.log(fibonacci.next().value); //2
console.log(fibonacci.next().value); //3
The logic behind the above code is simple. We declared fibGenerator using the keyword function*
. Then inside the function, we declared 2 variables curr and next, which correspond to the current and next number respectively. Then we create an infinite while loop so as to keep generating the next number in the fibonacci sequence. Then at each iteration, we yield the curr
value and then update curr to next value and next to curr + next value.
Usage in TypeScript
In TypeScript, the Generator
type is a built-in type that represents a generator function. The Generator
type takes three type parameters:
Yield Type (
YieldType
): This is the type of values that the generator yields using theyield
keyword.Return Type (
ReturnType
): This is the type of the value returned by the generator when it completes using areturn
statement.Next Type (
NextType
): This is the type of the argument that can be passed to thenext
method when iterating over the generator.
Example Syntax:
function* exampleGenerator(): Generator<number, undefined, undefined> {
yield 1;
yield 2;
yield 3;
}
const generator: Generator<number, undefined, undefined> = exampleGenerator();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }
/* after this, every time you call console.log(generator.next()),
you will get { value: undefined, done: true }
*/
Fibonacci code in TypeScript
function* fibGenerator(): Generator<number, void, unknown> {
let curr = 0;
let next = 1;
while (true) {
yield curr;
[curr, next] = [next, curr + next];
}
}
const fibonacci: Generator<number, void, unknown> = fibGenerator();
console.log(fibonacci.next().value); // 0
console.log(fibonacci.next().value); // 1
console.log(fibonacci.next().value); // 1
console.log(fibonacci.next().value); // 2
console.log(fibonacci.next().value); // 3
Real World Use Case
Now, you might wonder if these generators have a real world use case. They sure do have. One real-world use case for generators in JavaScript/TypeScript is in managing asynchronous control flow. Generators can simplify asynchronous code by allowing you to write asynchronous logic in a more synchronous style, making it easier to understand and maintain.
Consider an example using the fetch
API to make multiple asynchronous requests sequentially. In this case, the generator function can yield promises, and the control flow will pause until the promises are resolved.
function* fetchData(): Generator<Promise<any>, void, unknown> {
try {
const response1 = yield fetch('https://api.example.com/data1');
const data1 = yield response1.json();
const response2 = yield fetch('https://api.example.com/data2');
const data2 = yield response2.json();
console.log('Data 1:', data1);
console.log('Data 2:', data2);
} catch (error) {
console.error('Error fetching data:', error);
}
}
function runGenerator(generator: Generator<Promise<any>, void, unknown>) {
const iterate = (result: IteratorResult<Promise<any>, void>): void => {
if (result.done) {
return;
}
result.value
.then((data) => iterate(generator.next(data)))
.catch((error) => iterate(generator.throw(error)));
};
iterate(generator.next());
}
// Usage
const dataGenerator = fetchData();
runGenerator(dataGenerator);
In conclusion, generators in JavaScript are special functions that as the name says work as generators, i.e., generate something accordingly. And for generating some kind of result they use the yield
keyword.
I hope you learned something new today. You can reach out to me on: