Let’s talk about generators…
Generators, you ask?
Well, they’re these special functions in JavaScript…

They give you more control over the function execution process.
It’s like having a magic wand for your code!
You might not have used them much, or at all.
Perhaps you’ve been coding without realizing their potential.
Or maybe they seemed a little intimidating or unnecessary at first glance.
No worries, we’ve all been there.
But once you unlock the magic of generators, there’s no turning back.
You’ll find them incredibly useful in a variety of coding scenarios.
So, are you ready to journey into the world of JavaScript generators?
Let’s dive right in!
Deep dive into generators
To kickstart our journey let’s look at what’s so unique about generators.
Well unlike regular functions generators pause and resume their execution.
Intriguing isn’t it?
Let’s look at a quick example:
function* myGenerator() {
yield 'Hello';
yield 'World';
}
const iterator = myGenerator();
console.log(iterator.next()); // {value: 'Hello', done: false}
console.log(iterator.next()); // {value: 'World', done: false}
console.log(iterator.next()); // {value: undefined, done: true}
This example paints a clear picture of what’s happening.
Notice how the keyword function*
is used to define a generator.
That *
marks our function as a generator.
You can also see the yield
keyword.
yield
is how we pause execution in a generator.
Each time yield
is encountered, the generator gives a value back.
This value can be accessed using the .next()
method.
.next()
is a powerful method provided by generators.
In the example, iterator.next()
is called three times.
The first two calls to next()
return 'Hello'
and 'World'
respectively.
On the third call, the generator indicates it’s done and returns undefined
.
It’s as if the generator is saying: “That’s it, friend. I have nothing more to give.”
By now, you might be realizing that generators are pretty cool.
Indeed, they are!
In the next section, we’ll dig into why they are so beneficial.
Benefits of using generators
Lazy evaluation
Lazy evaluation is a core benefit of generators.
Simply put, it means “calculate only when necessary.”
In contrast, regular functions in JavaScript execute entirely and return the result.
Let’s say you want a sequence of numbers, but you’re not sure how many. Here’s how a generator helps:
function* numberGenerator() {
let number = 1;
while (true) {
yield number++;
}
}
const numbers = numberGenerator();
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 2
// you can continue this as long as you need
With generators, you get the next number only when you ask for it.
Better memory utilization
Generators don’t hold all the results in memory, they generate them on the fly.
Imagine you need a sequence of a million numbers.
With a regular function, you’d need to store all these numbers in an array, using up significant memory.
A generator is more efficient:
function* bigNumberGenerator() {
let number = 1;
while (number <= 1000000) {
yield number++;
}
}
const bigNumbers = bigNumberGenerator();
// Generate numbers as you need them, no need to store a million numbers in memory!
Handling asynchronous tasks more efficiently
Asynchronous code can get messy.
Promises and async/await help, but generators add another tool to our toolbox.
They can pause and resume, perfect for handling asynchronous operations.
function* fetchUser(userId) {
const response = yield fetch(`https://api.example.com/users/${userId}`);
const user = yield response.json();
return user;
}
In this code, the fetchUser
generator fetches a user from an API.
It doesn’t move to the next yield
until it has the result, keeping your async code clean and readable.
Real-world application: simulating a typing animation
Isn’t it mesmerizing to see typing animations on websites? They hold our attention and enhance the user experience.
Now, creating a typing animation has its challenges.
Traditional ways can be complex and a bit convoluted.
You’ve probably seen code like this:
let text = 'Hello, world!';
let index = 0;
function typeAnimation() {
if (index < text.length) {
document.body.innerHTML += text.charAt(index);
index++;
setTimeout(typeAnimation, 500);
}
}
typeAnimation();
The above function creates a typing effect, but it’s not very flexible.
You can’t easily control when it starts and stops or when it resumes.
Here’s where generators can simplify things for us.
Yes, generators, those special functions in JavaScript that can pause and resume their execution!
The beauty of generators is their ability to produce data on demand.
They don’t compute all values upfront.
Instead, they generate them when needed.
Let’s see how we can use a generator to make our typing animation.
We’ll create a generator function that yields a new character from our string each time it’s called.
Something like this:
function* typeAnimation(text) {
for(let char of text) {
yield char;
}
}
Imagine the possibilities with this generator function!
You now have more control over the typing animation.
In the next section, we’ll see how to put this generator to work.
We’ll step by step build our typing animation, using the power of generators.
Step-by-step guide to creating a typing animation using generators
Begin.
Create a new HTML file. Here’s the base layout:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Typing Animation</title>
</head>
<body>
<div id="type-here"></div>
<script src="index.js"></script>
</body>
</html>
Notice the type-here
div
? That’s where our animation goes.
Also, we’ve linked to index.js
. That’s our JavaScript file.
Defining the generator
JavaScript time.
Let’s create a generator function. Name it typeGenerator
.
function* typeGenerator(text) {
for(let char of text) {
yield char;
}
}
What’s happening here?
Our generator goes through each character in the text.
Then, it “yields” the character.
Easy, right?
Implementing the animation
Let’s animate.
Create a generator instance with your message.
const generatorInstance = typeGenerator("Coding Beauty");
Then, initiate a typing effect function. It will use our generator instance.
function startTypingEffect(elementId, generatorInstance) {
let element = document.getElementById(elementId);
setInterval(() => {
let result = generatorInstance.next();
if (!result.done) {
element.innerHTML += result.value;
}
}, 100);
}
And call the function:
startTypingEffect("type-here", generatorInstance);

Success!
Now, let’s explore more applications.
Further applications of generators
Asynchronous handling
Generators are pretty good at handling asynchronous tasks.
Running some action while allowing other code to run…
With generators, you can do this in a simpler way.
Here’s a code example:
function* asyncGenerator() {
yield new Promise(resolve => setTimeout(() => resolve('Task 1 completed'), 2000));
yield new Promise(resolve => setTimeout(() => resolve('Task 2 completed'), 3000));
}
And here’s how we can use this generator:
const asyncGen = asyncGenerator();
asyncGen.next().value.then(console.log); // Isn't this neat?
asyncGen.next().value.then(console.log);
Data streaming
Next up, generators are very helpful for data streaming.
Say you’re working with large datasets.
Often, you don’t need to process all the data at once.
With generators, you can handle data bit by bit.
This is called ‘lazy evaluation’.
Here’s a generator that does this:
function* dataStreamer(data) {
for (let item of data) {
yield item;
}
}
Let’s see how we can consume this generator:
const largeData = Array(1000000).fill().map((_, i) => i);
const streamer = dataStreamer(largeData);
for(let value of streamer) {
// process value, it's as simple as that!
}
redux-saga
Did you know that generators have a big role in redux-saga?
redux-saga is a library for managing side effects in applications.
In redux-saga, actions are handled by generators.
This makes testing and error handling easier.
Take a look at this simple saga:
import { call, put, takeEvery } from 'redux-saga/effects';
function* fetchData(action) {
try {
const data = yield call(Api.fetchUser, action.payload.userId);
yield put({type: "USER_FETCH_SUCCEEDED", data});
} catch (e) {
yield put({type: "USER_FETCH_FAILED", message: e.message});
}
}
function* mySaga() {
yield takeEvery("USER_FETCH_REQUESTED", fetchData);
}
In redux-saga, the saga middleware runs the generators when actions are dispatched:
import createSagaMiddleware from 'redux-saga';
import { createStore, applyMiddleware } from 'redux';
import mySaga from './sagas';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(mySaga); // And it's done!
In summary, generators make asynchronous operations in JavaScript simpler.
They can help optimize memory use when dealing with data streaming.
And in redux-saga, they help manage side effects in an efficient way.
So, why not try using generators in your next project?
Limitations and precautions when using generators
Caution ahead!
Yes, generators are powerful.
But they’re not omnipotent.
They have their limitations.
For instance, they don’t work well with ‘return’ statements.
Consider this code:
function* myGenerator() {
yield 'Hello';
return 'End';
yield 'This will not be executed';
}
Calling next()
on myGenerator
stops at ‘End’.
The third yield
statement isn’t reached.
Now you know why!
Generator pitfalls
It’s easy to get caught in a forever loop.
For example:
function* infiniteGenerator() {
let index = 0;
while (true) {
yield index++;
}
}
Use infiniteGenerator
with caution. Or your code could run indefinitely!
Beware of Promises…
Generators and asynchronous operations can get tricky.
For example:
function* asyncGenerator() {
const response = yield fetch('https://api.example.com/data');
return response.json();
}
This code won’t work as expected.
Generators don’t implicitly handle Promises.
But don’t worry! Libraries like co
can help.
They let generators and Promises play nicely.
Embrace these cautions and become a master of generators.
Final thoughts
Wrap-up.
Generators, unique.
Yield, powerful.
No more pulling everything at once, just piecemeal.
Need more data? .next()
is your friend.
Remember:
function* typingGenerator(str) {
for(let char of str) {
yield char;
}
}
Easy, elegant.
It’s JavaScript’s brilliance.
Generator’s versatility? Undeniable. We’ve only scraped the surface.
Expand your horizons.
Asynchronous tasks? Data streaming? Side effects management in Redux-Saga?
Yes, generators work.
But remember, limitations exist. They don’t fit everywhere. Be cautious.
Explore more. Experiment. Push your code’s boundaries. Unleash Generators’ power.
Keep learning.
Onwards to more JavaScript adventures!
Every Crazy Thing JavaScript Does
A captivating guide to the subtle caveats and lesser-known parts of JavaScript.
