So I have a paragraph of text, and I want to replace one word from the paragraph with a new word every second. The paragraph should change gradually over time. Seems quite simple but I can’t seem to make this work in observable as I get into an update loop where changes happen must faster than my timer.
Maybe you have some code to show?
You can see a minimal test here: Test: update text in place / Daniel Howe / Observable
The goal is to be able to call update() on a timer, ever X milliseconds.
Here’s something you can play around with:
See also:
Thanks Brian, that’s helpful. Two quick follow-ups:
- What I’d really like to do though is separate the update function from the timer. But when I do the following, I’m back in a busy loop:
{
while (true) {
yield Promises.delay(1000, update());
}
}
- The second regards timing. Correct me if I’m wrong, but this will fire every 1000 ms regardless of how long the update function takes to run (for example, if it calls out to an API). Is there a way, as in straight JS with setTimeout and a self-calling function, to call the function 1 second after it has completed?
@shadoof suggested the variation below, yet this still cause updates at a much higher rate than specified (I think, though I’m not sure, that this is bc updating the mutable ‘text’ cause the update() function itself to be re-triggered)
{
while (true) {
yield Promises.tick(3000).then(update);
}
}
You need to await
the promise:
yield await Promises.tick(3000).then(update);
If you want an example for text replacement over a whole document, take a look the first cell in (Attempt at) Embedding a Video Into an SVG / Fabian Iwand / Observable. To trigger the replacement, look for the line “If that still hasn’t put you off, please click here.” and click “here”.
(first post to the forum so apologies in advance for any protocol or formatting issues)
Thanks @mootari. Although await
ing the Promises seems to be the right thing to do, changing a mutable
cell reference in the update
function still triggered unwanted recalculations when I tested. The solution was to get the update
function to return the changed value and only update the display
cell after each tick:
ticker = {
while (true) {
yield await Promises.tick(3000).then(() => (display.innerText = update()));
}
}
Thanks for all the suggestions! This is the simplest bit I found that works without triggering additional cell updates. I wonder if anyone has considered a “manual mode” where cell updates have to be explicitly invoked…
{
while (true) {
yield await Promises.delay(3000, display.innerText = update(display.innerText));
}
}
You don’t have to trigger updates. But at this point it would help a lot to have a link to your notebook, as the simplified example that you shared earlier doesn’t seem to match what you’re describing anymore.
That example still doesn’t tell me why your timer cell being a generator poses a problem.
In any case, you can just use a setTimeout loop:
let timeoutId;
const loop = () => {
// do stuff, then ...
timeoutId = setTimeout(loop, 3000);
};
invalidation.then(() => clearTimeout(timeoutId));
loop();