Skip to content Skip to sidebar Skip to footer

What Is Meant By Saying That Events Happened Before We Started Listening To Them?

While reading for Javascript Promises, I have come across few blogs/articles where the Promise implementation is first compared with the Javascript Event model and then the callbac

Solution 1:

They mean pretty much what they literally said: The event occurred before we started listening for it. For instance, suppose we show a button, but there's a delay of two seconds before we add an event handler listening for clicks on that button. If the user clicks within those two seconds, the event occurs before we were listening for it.

Example:

functionrun() {
  // Show a buttondocument.getElementById("btn").style.display = "inline";
  
  // Wait two seconds before we hook up a handlersetTimeout(function() {
    document.getElementById("btn").addEventListener("click", function() {
      console.log("Got a click");
    }, false);
  }, 2000);
  
  // If the user clicks within those two seconds, the event happened// before we were listening for it.
}

// This is just stuff to make sure the user is ready before showing the buttonvar counter = 4;
tick();

functiontick() {
  if (counter == 0) {
    document.getElementById("get-ready").style.display = "none";
    run();
  } else {
    document.getElementById("countdown").innerHTML = counter--;
    setTimeout(tick, 1000);
  }
}
<pid="get-ready">Get ready, a button will appear in <spanid="countdown"></span>, click it as soon as it appears, then again a couple of seconds later:</p><inputstyle="display: none"type="button"id="btn"value="Click me as soon as I appear, and again a couple of seconds later">

What I was looking for was some generic cases where events are fired before we attach listeners.

Well, the above is quite generic: Consider the usual practice of putting your JavaScript in a script tag at the end of the document body, just before the closing </body> tag, or using the async or defer attributes on it. That means there is a brief period of time when any buttons on the page can be clicked, but we haven't hooked up handlers on them yet. If there's a temporary network glitch, for instance, and it takes two seconds instead of the usual ~65ms to load your JavaScript file, your users would click the button (trigger the event) before you hooked your handler (were listening for it).

Alternately, this used to be a good example, but no longer happens on modern browsers as far as I can tellKaiido says it still does on WebKit browsers, although I couldn't make it happen on iOS Safari: Adding an img to a document and expecting to get the load event from code like this:

var img = document.createElement("img");
img.src = "path/to/the/image.png";
img.addEventListener("load", function() {
    // Handle the fact it loaded
}, false);
img.addEventListener("error", function() {
    // Handle the fact it failed
}, false);
someOtherElement.appendChild(img);

On the surface, it looks like there's no race condition there because of JavaScript's run-to-completion semantics and the fact that by default our JavaScript code runs on a single UI thread. But there is a race condition there, because while our code will follow a single thread, the browser is not single-threaded. So this can happen:

  1. img.src = "path/to/the/image.png";.
  2. The browser's resource management code goes looking for the image.
  3. Resource management finds it in cache and doesn't need to re-verify, so associates the image data with the element and triggers the load event.
  4. The event system goes to queue tasks for callbacks to any load event handlers that are present on the element, doesn't find any, and doesn't queue any tasks.
  5. We attach our handlers and append the element.
  6. Our code runs to completion.
  7. We never get either a load or error event callback.

In contrast, if we hook first and then set src:

var img = document.createElement("img");
img.addEventListener("load", function() {
    // Handle the fact it loaded
}, false);
img.addEventListener("error", function() {
    // Handle the fact it failed
}, false);
img.src = "path/to/the/image.png";          // <=== Moved
someOtherElement.appendChild(img);

Then the same scenario plays out like this:

  1. We attach our handlers.
  2. img.src = "path/to/the/image.png";.
  3. The browser's resource management code goes looking for the image.
  4. Resource management finds it in cache and doesn't need to re-verify, so associates the image data with the element and triggers the load event.
  5. The event system goes to queue tasks for callbacks to any load event handlers that are present on the element. It finds one, so it queues a task to call it when the task queue is next empty.
  6. We append the element.
  7. Our code runs to completion.
  8. The task loop picks up the next task from the queue, which happens to be our load callback.
  9. The task loop calls our callback.

Now, it happens that this behavior was sufficiently irksome that modern browsers don't do it anymore (although Kaiido says it still does on WebKit, I can't confirm that). Although I still code as though it does (the second example), because it could, it's a valid way for the browser to work.

Post a Comment for "What Is Meant By Saying That Events Happened Before We Started Listening To Them?"