Generators.observe -- parsing the documentation

I am trying to parse this piece of documentation from the observable framework page on Generators.

observe(initialize)

Source · Returns an async generator that immediately invokes the specified initialize function, being passed a change callback function, and yields the passed value whenever change is called. The initialize function may optionally return a dispose function that will be called when the generator is terminated.

const hash = Generators.observe((change) => {
  const changed = () => change(location.hash);
  addEventListener("hashchange", changed);
  changed();
  return () => removeEventListener("hashchange", changed);
});

If I understand correctly, what this means is that:

  1. The call
    const gen = Generators.observe(initialize)
    where initialize is a function, returns an async generator gen.

  2. When gen is created, the initialize function is called as initialize(change) where
    change is a function that we don’t see; it’s an internal callback function.

  3. The initialize function does some kind of initialization (d’oh) and then sets up conditions
    under which the change callback is executed. For example, change could get added as an eventListener.

  4. Whenever the situation created by initialize leads to the change function being called, the generator gen yields whatever argument was passed to change. So in some sense change functions like calling yield out of this generator.

  5. The return value from the generator is a function that does some kind of cleaning up.

Is this the correct picture? I am having trouble getting anything to work so I want to at least see if I have the right conceptual picture.

Your understanding sounds right to me! I’d be happy to take a look at more specific examples where things aren’t working as expected, if you’re able to share them — especially if there are smaller examples where you’re able to isolate the unexpected behavior.

One way to think about Generators.observe is that it adapts an asynchronous “push” mechanism (callbacks) to an asynchronous “pull” mechanism (iteration). The change callback lets you “push” a new value (in this case, whenever you receive a hashchange event, you propagate it through the generator). Whereas generators are a pull mechanism because you iterate over them, e.g. using a for await loop. So, Generators.observe adapts the “push” mechanism of calling change to the “pull” mechanism of a generator.

(Internally, this means that Generators.observe needs to store the most-recently pushed value so that it’s available when the next value is pulled from the generator. There’s also Generators.queue which is very similar, but it tracks the queue of pushed values rather than only tracking the most-recently pushed value. Generators.observe is potentially lossy, while Generators.queue is not.)

The Observable Runtime happens to use generators as the basis of its reactivity, but since the “push” mechanism is common, we provide Generators.observe as an adapter.

Thanks! I was trying to reproduce this notebook: Sampling from the Normal Distribution - Animation / Jeremy Teitelbaum | Observable
using Framework to get a feel for what’s involved. I have a version of it here:
Sampling from the standard normal distribution | Sampling

In the notebook version I have a “reset” button that restarts the animation. To do this I just needed to mention the button in the generator. In framework, I want a button to restart the async generator. But I’m not sure how to do this. Is this what Generator.observe is for or am I completely off base?

1 Like

You can use the same technique you had in the notebook. So, declare your restart button:

const start = view(Inputs.button("Restart"));

Then change your samples code block to reference start:

start; // re-initialize samples when the restart button is clicked
const samples = (async function* () {
  var allm = [];
  for (let j = 0; true; ++j) {
    const s = samplefn(nsamples);
    allm.push(s);
    yield { m: s, accum: allm };
    await new Promise((resolve, reject) => setTimeout(resolve, inter));
  }
})();
1 Like