Fundamentals 7 min read

Understanding JavaScript Generators: Beyond Async/Await and Infinite Sequences

This article clarifies the misconception that JavaScript generators are only for async, explains their underlying pause‑and‑resume mechanism, demonstrates how they can model infinite sequences such as prime numbers by translating Haskell concepts, and shows practical API designs using generators.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Understanding JavaScript Generators: Beyond Async/Await and Infinite Sequences

In a recent discussion a common misunderstanding was highlighted: many developers immediately associate JavaScript generators with asynchronous programming, which the author argues is a deep misconception.

Generators are not merely a tool for simulating async/await; early frameworks like co used them among many language features (if, functions, variables) to emulate async behavior, but the causal relationship is that both generators and async/await share the same low‑level capability of pausing function execution while preserving the execution context.

The author contends that generators have a far broader horizon than async/await. They embody the abstraction of an "infinite or unbounded sequence," opening new programming paradigms.

As an illustration, the Haskell homepage showcases a concise prime‑number generator using lazy evaluation and infinite lists:

primes = filterPrime [2..]
  where filterPrime (p:xs) =
          p : filterPrime [x | x <- xs, x `mod` p /= 0]

This Haskell snippet creates an endless list of primes, a pattern that is difficult to replicate in most languages because it relies on lazy infinite lists.

To bring the same idea to JavaScript, the article proposes using generators to represent the infinite list [2..] and to implement a custom filter that works on such streams.

function* integerRange(from, to) {
    for (let i = from; i < to; i++) {
        yield i;
    }
}

A generator‑based filter for infinite sequences is then defined:

function* filter(iter, condition) {
    for (let v of iter) {
        if (condition(v)) {
            yield v;
        }
    }
}

The core filterPrime generator mirrors the Haskell logic:

function* filterPrime(iter) {
    let p = iter.next().value;
    let rest = iter;
    
    yield p;
    for (let v of filterPrime(filter(iter, x => x % p != 0)))
        yield v;
}

Using JavaScript's async capabilities, the prime sequence can be printed with a one‑second pause between values:

function sleep(d) {
    return new Promise(resolve => setTimeout(resolve, d));
}
void async function() {
    for (let v of filterPrime(integerRange(2, Infinity))) {
        await sleep(1000);
        console.log(v);
    }
}();

Beyond infinite lists, generators are useful for wrapping APIs that produce indeterminate sequences, such as repeatedly executing a regular expression:

function* execRegExp(regexp, string) {
    let r = null;
    while (r = regexp.exec(string)) {
        yield r;
    }
}

A simple lexical analyser can then be built by iterating over the tokens produced by this generator:

let tokens = execRegExp(/let|var|\s+|[a-zA-Z$][0-9a-zA-Z$]*|[1-9][0-9]*|\+|-|\*|\/|;|=/g, "let a = 1 + 2;")
for (let s of tokens) {
    console.log(s);
}

The article concludes that generators are a powerful language feature that opens the door to "infinite" mathematical concepts and cleaner API designs, encouraging developers to explore uses beyond merely mimicking asynchronous code.

JavaScriptFunctional Programmingasync/awaitGeneratorsInfinite SequencesHaskellPrime Numbers
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.