🏠 back to Observable

Change words in paragraph on timer ?

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:

  1. 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());
      }
    }
  1. 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 :point_right:here.” and click “here”.

(first post to the forum so apologies in advance for any protocol or formatting issues)
Thanks @mootari. Although awaiting 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.

Link: Example: Replaceable Writing / Daniel Howe / Observable

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();

Yes. this is what I did for the same example in vanilla JS. Documentation [1, 2, 3], seems to suggest this is not the proper way to do it in observable, but perhaps it is OK if the timeouts are cleared…