async programming
es6
generators
javascript
loops

How to perform an async task against es6 generators in loop

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Using ES6 generators for async tasks in loops is a legacy but still relevant pattern in codebases that predate wide async and await adoption. The core idea is that a generator yields promises, and a runner resumes the generator when each promise resolves. When designed well, this gives sequential, readable flow control without deeply nested callbacks.

Why Generators Were Used for Async Control

A generator can pause execution at each yield, then continue later with a value. That behavior maps naturally to async operations such as HTTP requests or database calls.

At each step:

  1. generator yields a promise
  2. external runner waits for resolution
  3. runner feeds resolved value back with next(value)
  4. generator continues to next line

This pattern keeps loop logic linear and avoids manual callback chaining.

Minimal Generator Runner

First, define a helper that executes a generator function and resolves all yielded promises.

javascript
1function run(generatorFn) {
2  const iterator = generatorFn();
3
4  return new Promise((resolve, reject) => {
5    function step(method, arg) {
6      let result;
7
8      try {
9        result = iterator[method](arg);
10      } catch (err) {
11        reject(err);
12        return;
13      }
14
15      const value = result.value;
16      const done = result.done;
17
18      if (done) {
19        resolve(value);
20        return;
21      }
22
23      Promise.resolve(value)
24        .then((val) => step("next", val))
25        .catch((err) => step("throw", err));
26    }
27
28    step("next");
29  });
30}

This runner supports both success and error propagation.

Sequential Async Loop with Generators

Now use the runner with a generator that performs async work in sequence.

javascript
1function fakeFetch(id) {
2  return new Promise((resolve) => {
3    setTimeout(() => resolve(`item-${id}`), 100);
4  });
5}
6
7function* processItems() {
8  const ids = [1, 2, 3, 4];
9  const results = [];
10
11  for (const id of ids) {
12    const data = yield fakeFetch(id);
13    results.push(data.toUpperCase());
14  }
15
16  return results;
17}
18
19run(processItems)
20  .then((result) => console.log(result))
21  .catch((err) => console.error(err));

Because each yield waits before continuing, this loop is strictly sequential.

Running Async Work Concurrently from a Generator

Sometimes loop steps are independent and should run in parallel. You can still use generators by yielding Promise.all.

javascript
1function* processItemsConcurrently() {
2  const ids = [1, 2, 3, 4];
3  const promises = ids.map((id) => fakeFetch(id));
4  const values = yield Promise.all(promises);
5
6  return values.map((v) => v + "-done");
7}
8
9run(processItemsConcurrently)
10  .then((result) => console.log(result))
11  .catch((err) => console.error(err));

This keeps generator style while improving total latency for independent requests.

Error Handling in Loop Steps

Errors from yielded promises should be caught either inside generator logic or at runner boundary.

javascript
1function unstableFetch(id) {
2  return new Promise((resolve, reject) => {
3    if (id === 3) {
4      reject(new Error("bad id"));
5    } else {
6      resolve(`ok-${id}`);
7    }
8  });
9}
10
11function* resilientLoop() {
12  const ids = [1, 2, 3, 4];
13  const output = [];
14
15  for (const id of ids) {
16    try {
17      const v = yield unstableFetch(id);
18      output.push(v);
19    } catch (err) {
20      output.push(`fallback-${id}`);
21    }
22  }
23
24  return output;
25}
26
27run(resilientLoop).then(console.log).catch(console.error);

This is useful when some loop items may fail but overall processing should continue.

Cancellation and Timeouts

Generator patterns do not provide built-in cancellation. If needed, include timeout-wrapped promises or external cancellation tokens.

javascript
1function withTimeout(promise, ms) {
2  return Promise.race([
3    promise,
4    new Promise((_, reject) =>
5      setTimeout(() => reject(new Error("timeout")), ms)
6    )
7  ]);
8}

Then yield withTimeout(...) inside your generator loop.

Relationship to async and await

Modern JavaScript generally prefers async and await because it is simpler for most teams. Still, understanding generator-based async flow helps when maintaining older libraries such as co style code.

Equivalent sequential logic with async and await is shorter, but generator runners remain a valid compatibility technique.

When to Keep or Replace Generator Async Loops

Keep generator approach when:

  • codebase already uses generator runner abstraction heavily
  • migration cost to async and await is currently high
  • behavior is stable and well-tested

Replace when:

  • new contributors struggle to maintain runner internals
  • debugging stack traces becomes difficult
  • partial migrations have created mixed patterns

Consistency matters more than pattern preference.

Common Pitfalls

  • Yielding non-promise values accidentally and misreading loop behavior.
  • Forgetting to handle rejected promises in the runner and losing errors.
  • Assuming generator loops are concurrent by default when they are sequential.
  • Mixing generator style with async and await in one flow without clear boundaries.
  • Reimplementing runners inconsistently across modules instead of reusing one tested utility.

Summary

  • ES6 generators can manage async loop workflows by yielding promises.
  • A runner function is required to drive next and throw transitions.
  • Generator loops are naturally sequential unless you explicitly yield Promise.all.
  • Proper error propagation and timeout handling are essential for reliability.
  • For legacy systems, generator async loops remain maintainable when centralized and tested.

Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Basis Lab
No course covers this one yet

Basis Lab writes one for you from a sentence about what you want to be able to do, then teaches it and checks you understood. Your first course is free.

Build my course
Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions