0

Summary

I'm writing code with a lot of alerts/confirms and I'm finding it a pain to keep everything rendering the way it should. I just want the snippet below to work as expected. First "Before" should be displayed, then it should wait for the user to confirm the alert box. Then "After" should be displayed.

document.getElementById('display').innerHTML = "Before";
// ALERT
document.getElementById('display').innerHTML = "After";

Tried

I've tried the naive way & the way recommended on a million dupes all over SO with setTimeout(). (I tried zero & non-zero time.)

document.getElementById('display').innerHTML = "Before";
alert("Alert Text")
document.getElementById('display').innerHTML = "After";
document.getElementById('display').innerHTML = "Before";
setTimeout(function() {
    alert("Alert Text")
    }, time);
document.getElementById('display').innerHTML = "After";

In the first case, the stuff before hasn't finished rendering, before alert halts execution. And in the second, the stuff below has already begun rendering, replacing the stuff above.

Ideas

One can put everthing below the alert within the setTimeout() block. Not a terrible hack for one alert. But with lots of alerts, that means lots of nested setTimeout(), which seems wrong on several levels!

A simple sleep() would resolve everything. But the accepted SO answer warns never to use sleep() equivalents in JS, preferring setTimeout() instead. So back to square 1!

TL;DR: What's the best way to use alert() so that the HTML before & after are rendered in order?

AviFS
  • 336
  • 2
  • 12
  • The dom renders from top to bottom, so if there is an alert before the rest of the dom can render, nothing will be displayed. If you do not like the default behaviour of the alerts, why not create your own? – Spangle Oct 07 '20 at 04:13
  • @Spangle Thanks for responding; not sure I'm on board though! The behavior I'm looking for definitely seems common & useful enough that I'd think there should be known better/worse ways to do it... What am I missing? – AviFS Oct 07 '20 at 04:19
  • Curious to hear what the more idiomatic ways of doing it are. I'm sure people have dealt with it before! But if nothing else, I gave two ways I'd go about doing it myself. Redefining the default behavior seems a bit over-kill for what I'm doing! – AviFS Oct 07 '20 at 04:22
  • Alerts are a pretty in your face kind of thing, freezing everything until they are closed. This is why alerts are usually custom made. – Spangle Oct 07 '20 at 04:30
  • I’m afraid we’ll have to move to a chatroom soon! But if it helps, I actually want to use the `confirm()`, the alert I just figured was more relevant to most people. So maybe there’s a better way of doing yes/no pop-ups, then? Those certainly seem ubiquitous enough that there should be a standard method that can be used out of the box! – AviFS Oct 07 '20 at 04:38
  • Sounds like your real problem is with "callback hell" (too many nested callbacks). There are a number of ways to solve this, including `async`. – Brian McCutchon Oct 07 '20 at 04:59

1 Answers1

0

Don't use alert() or any other prompts, not even for debugging.
These are old artifacts of the web APIs and should never be used anymore.

They have the very weird property of blocking the current js execution until closed. Different browsers will handle it differently, for instance in Firefox, they do "spin the event-loop" when something like that happens, letting the rendering part of the event-loop still alive and thus in this browser you'll see the updated values painted. In Chrome however they do completely lock the event-loop, and no rendering will occur.

So the solution is to never rely on these methods and instead to build your own modal using HTML and CSS. There is in HTML the <dialog> element which helps a lot in doing such modal from scratch, but any UI library has its own modal (example).

Then you just have to listen for the close event or equivalent to know when the user did close that modal:

const dial = document.getElementById("dial");

document.getElementById("display").textContent = "Before";

dial.showModal();

console.log("js execution is not blocked");
// when the modal is closed
dial.addEventListener("close", (evt) => {
  document.getElementById("display").textContent =
  "After";
}, { once: true });
<div id="display"></div>
<dialog id="dial">
  <form method="dialog">
    <p>My awesome alert message</p>
    <button value="default">OK</button>
  </form>
</dialog>

If you need a more linear way of coding than all these callbacks, you can always promisify it and use an async / await function:

const dial = document.getElementById("dial");

function myAlert(message) {
  dial.querySelector("p").textContent = message;
  dial.showModal();
  return new Promise( (res) => {
    dial.addEventListener("close", () => res(), { once: true });
  });
}

(async () => {

  document.getElementById("display").textContent = "Before";
  await myAlert('hello');
  document.getElementById("display").textContent = "After";
  await myAlert('bye');
  
})();
dialog {
  min-width: 150px;
}
dialog > form > button {
  float: right;
}
<div id="display"></div>
<dialog id="dial">
  <form method="dialog">
    <p>My awesome alert message</p>
    <button value="default">OK</button>
  </form>
</dialog>
Kaiido
  • 123,334
  • 13
  • 219
  • 285