I am trying to parse this piece of documentation from the observable framework page on Generators.
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:
-
The call
const gen = Generators.observe(initialize)
where initialize
is a function, returns an async generator gen
.
-
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.
-
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
.
-
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.
-
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