🧙🏽‍♂️ JavaScript Generator - The Iterator-Observer: How I Discovered JavaScript’s Two-Faced Wizard
If you’ve ever tried to manage asynchronous code in JavaScript, you know how messy things can get. For me, it all started with a simple question: “Is there a way to write asynchronous code that feels… synchronous?”
That question led me down a rabbit hole — and at the bottom, I met a two-faced wizard: the JavaScript Generator.
👋🏽 First Impressions: The Pulling Mechanism
I still remember the first time I used a generator. At that point, I only saw it as a fancy way to produce values one at a time.
Something like this:
function* produceValues() {
yield 1;
yield 2;
yield 3;
}
const gen = produceValues();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
Each time I called .next()
, it felt like I was pulling a rabbit out of a magician’s hat. The function paused at each yield
, waiting patiently for me to ask again. This was my first true “aha!” moment — a function that doesn’t finish all at once, but takes breaks and continues later. That’s powerful.
📨 My Second Surprise: The Pushing Mechanism
But then I stumbled upon something even more surprising: I could send values back into the generator!
At first, this sounded odd. How could a function accept input while it’s running?
But here’s what I tried:
function* greetGenerator() {
const name = yield "What's your name?";
console.log(`Hello, ${name}!`);
const language = yield "What language do you love?";
console.log(`Ah, ${language}! Excellent choice.`);
}
const greeter = greetGenerator();
console.log(greeter.next().value); // "What's your name?"
console.log(greeter.next("Ekene").value); // Hello, Ekene! → "What language do you love?"
console.log(greeter.next("JavaScript")); // Ah, JavaScript! Excellent choice.
This was the second “wow” moment for me. The value I passed into .next("Ekene")
didn’t just vanish. It went into the function, becoming the result of the previous yield
.
That’s when I realized — this generator isn’t just an iterator… it’s also an observer.
đź§ Lightbulb Moment: Managing Async Code with Generators
The moment I truly appreciated the power of generators was when I connected them to asynchronous programming.
I was already tired of writing deeply nested .then()
calls. Even async/await
had its limitations in some older environments. That’s when I read about using generators to manage promises, and how libraries like co
, taskjs
, and bluebird
used them under the hood.
So I gave it a go. I wrote this:
function fetchUser() {
return new Promise((resolve) => {
setTimeout(() => resolve({ id: 1, name: "Ekene" }), 1000);
});
}
function fetchPosts(userId) {
return new Promise((resolve) => {
setTimeout(() => resolve(["Post 1", "Post 2"]), 1000);
});
}
And then, I wrote my generator like this:
function* loadData() {
const user = yield fetchUser();
console.log("User:", user);
const posts = yield fetchPosts(user.id);
console.log("Posts:", posts);
}
It looked synchronous, but it was fully asynchronous. That’s when I realized I needed a runner function — a way to orchestrate the pulling and pushing automatically.
🪄 Enter spawn()
: My Generator Runner
This little helper changed everything:
function spawn(genFunc) {
const gen = genFunc();
function step(nextF) {
let next;
try {
next = nextF();
} catch (err) {
return Promise.reject(err);
}
if (next.done) {
return Promise.resolve(next.value);
}
return Promise.resolve(next.value).then(
(v) => step(() => gen.next(v)),
(e) => step(() => gen.throw(e))
);
}
return step(() => gen.next());
}
And using it:
spawn(function* () {
const user = yield fetchUser();
const posts = yield fetchPosts(user.id);
console.log("Final Output:", { user, posts });
});
At this point, I paused and smiled. I had found the missing link between callback chaos and synchronous-style async code. It was a generator. A kind of asynchronous storyteller who pauses and resumes, listens and responds.
✨ Generators Today
These days, I don’t write generator-based async flows often, because JavaScript has given us async
and await
. But knowing that those are built on this very pattern makes me respect generators even more.
Behind every await
is a little two-faced wizard — pulling and pushing — keeping the story moving forward, one yield at a time.
đź§Ş Review Time
Let me see if you caught the magic:
- What does
.next()
do in a generator function? - How does
.next(value)
allow you to send values into a paused generator? - Why are generators called both iterators and observers?
- What role does a
spawn()
function play in running generator-based async flows? - How do modern
async
/await
features relate to generator functions?