Observable 2.0 Generators Corollary

The official Upgrading to Notebooks 2.0 Guide (§ Generators) details how generator cells are converted in 2.0 using explicit generator functions or IIFEs (async function*).

A practical variation of this pattern occurs when using event streams like Generators.queue() to create reactive filters (e.g., listening to a Vega-Lite brush or brush_x signal on a barChart).

The notebook Generators in Observable 2.0 illustrates a simple workaround using for await:

filter1 = {
    const eventStream = Generators.queue((notify) => {
    notify("a");
    });

    // ❌ WORKED IN 1.0; BROKEN IN 2.0:
    for (const value of eventStream) {
    let filters = await value;
    // Perform any optional processing before re-yielding to the notebook
    yield filters;
    }
}


filter2 = {
    const eventStream = Generators.queue((notify) => {
    notify("a");
    });

    // ✅ FIXED IN 2.0 BY SIMPLY ADDING 'await':
    for await (const value of eventStream) {
    let filters = await value;
    // Perform any optional processing before re-yielding to the notebook
    yield filters;
    }
}

Yes, that’s right, although you don’t need the inner await within the for-await loop.

So before it was:

for (const promise of generator) {
  const value = await promise;
  console.log(value);
}

And now it is:

for await (const value of generator) {
  console.log(value);
}

See for await…of and async function*.

The reason for this change is that Old Observable’s generators predate widespread browser support for async generators, and hence they were implemented as (synchronous) generators that yield promises. Since browsers now widely support asynchronous generators, New Observable uses that. (You can compare the new implementation to the old implementation.)

Perhaps we should add backwards compatibility for this difference in behavior in the 2018 standard library in New Observable… but I was hoping that the distinction was small enough to not be worth the trouble.