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:

  1. What does .next() do in a generator function?
  2. How does .next(value) allow you to send values into a paused generator?
  3. Why are generators called both iterators and observers?
  4. What role does a spawn() function play in running generator-based async flows?
  5. How do modern async/await features relate to generator functions?

<
Previous Post
🎨 Personal Blog: The Day My 6-Year-Old Taught Me Colours
>
Next Post
🚀 JavaScript Promises: A LEGO Spaceship Adventure