2

I have a while loop going with an alert() inside. Inside the while loop, I want to change the content of an element, in this case with a counter.

I have a snippet which shows the idea.

var test = document.querySelector('div#test');
var limit = 5;
var counter = 0;
do {
  counter++;
  test.textContent = counter;
  console.log(counter)
  alert('waiting …');
} while (counter<limit);
div#test {
  text-align: center;
}
<div id="test"></div>

For testing purposes, I also include a console.log() but in the snippet it’s not working like a normal console.

I know that alert() is very disruptive, and blocks other activity. However, I find that somehow it prevents the element content from being changed, even though that statement is before the alert() in the loop. However, the console.log() statement is working, even though you can’t see it in the SO snippet. On a browser, such as Firefox and Safari, the console.log() prints the new value, but the element content stays unchanged until the end.

I also want the change the CSS style of the element, but I’ve left that off the sample. In any case it doesn’t work either.

I’m curious about what seems to be inconsistent behaviour. What is happening here, and is there a way to allow changes to the element to occur when there’s an alert() going on?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Manngo
  • 14,066
  • 10
  • 88
  • 110
  • 2
    DOM changes are only processed after all javascript has finished running. To do what you want, you'll need to construct something that uses setTimeout or setInterval. – Peter B Jul 04 '23 at 11:32
  • @PeterB I wasn’t aware of that. Are you able to give a reference for that? – Manngo Jul 04 '23 at 11:33
  • I don't have a list of references ready but I think this answer explains it rather well: https://stackoverflow.com/a/43508576/1220550 – Peter B Jul 04 '23 at 12:26
  • @PeterB, no, the DOM is processed synchronously. What happens is that `alert()` will also block the *rendering* part of the event loop. But the DOM is already updated. – Kaiido Jul 04 '23 at 13:01

1 Answers1

2

Your code is sync meaning you change the counter inside 1 JS task without allowing the browser to re-render the page. The rendering happens in the end of a JS task. To allow the browser to render you should split your logic into JS tasks. setTimeout and requestAnimationFrame create a new JS task for example.

We have a problem with Firefox though. Seems it optimizes the rendering and if several JS tasks are queued, it re-renders after all the queue is complete. So I've added 10ms delay to setTimeout. But requestAnimationFrame works fine in all browsers:

var test = document.querySelector('div#test');

const wait = () => new Promise(resolve => setTimeout(resolve, 10));

(async()=>{

  var limit = 5;
  var counter = 0;
  do {
    counter++;
    test.textContent = counter;
    console.log(counter);
    alert('waiting …');
    await wait();
  } while (counter<limit);

})();
div#test {
  text-align: center;
}
<div id="test"></div>

A simplified solution:

var test = document.querySelector('div#test');

var limit = 5;
var counter = 0;

const count = () => {
    counter++;
    test.textContent = counter;
    console.log(counter);
    alert('waiting …');
    counter<limit && setTimeout(count, 10);
};
count();
div#test {
  text-align: center;
}
<div id="test"></div>

We could also use requestAnimationFrame:

var test = document.querySelector('div#test');

var limit = 5;
var counter = 0;

const count = () => {
    counter++;
    test.textContent = counter;
    console.log(counter);
    alert('waiting …');
    counter<limit && requestAnimationFrame(count);
};
requestAnimationFrame(count)
div#test {
  text-align: center;
}
<div id="test"></div>
Alexander Nenashev
  • 8,775
  • 2
  • 6
  • 17