skip to Main Content

According to MDN, messages in the JavaScript event loop "run to completion".
(https://developer.mozilla.org/docs/Web/JavaScript/Event_loop#run-to-completion)

But I have created a case where this does not seem to happen.

abortController1 = new AbortController()
abortController2 = new AbortController()

abortController1.signal.addEventListener("abort", () =>
{
    console.log("abort 1 start")
    abortController2.abort()
    console.log("abort 1 end")
})

abortController2.signal.addEventListener("abort", () =>
{
    console.log("abort 2")
})

abortController1.abort()

Output:

abort 1 start
abort 2
abort 1 end

I was expecting to see this output:

abort 1 start
abort 1 end
abort 2

Can someone please explain what’s going on here?

EDIT (possible clues) –

2

Answers


  1. When an event is triggered directly by code, it runs the listeners synchronously. The MDN documentation for dispatchEvent() explains this:

    Unlike "native" events, which are fired by the browser and invoke event handlers asynchronously via the event loop, dispatchEvent() invokes event handlers synchronously. All applicable event handlers are called and return before dispatchEvent() returns.

    While this doesn’t mention abort() specifically, I see no reason why it wouldn’t be treated similarly.

    Login or Signup to reply.
  2. It’s just an example of a synchronous event.

    The fire an event algorithm is synchronous. Another notorious example where this algo is called synchronously is in HTMLElement#click(), or EventTarget#dispatchEvent() (see this very related Q/A for more details).
    The confusion comes from the fact that in most cases this algorithm is actually wrapped in a queue a task call, or from an algorithm that runs in parallel.

    In the case of AbortController#abort() the signal abort algorithm will run the abort steps synchronously, which will fire the abort event, also synchronously.
    This is a pure design choice. It was deemed that consumers of the AbortSignal would want to react synchronously, so that after your call to abort() you can be sure the side-effects to this call would already have kicked in.


    Below snippet tests a non exhaustive list of other such synchronous events from HTML:

    const inp = document.querySelector("input");
    inp.oninvalid = e => console.log("invalid event fired");
    console.log("checking input validity");
    inp.checkValidity();
    console.log("checked");
    
    console.log("");
    
    // Doesn't work in Firefox
    const frame = document.createElement("frame");
    frame.name = "target";
    frame.src = "about:blank";
    frame.onload = e => console.log("frame load event fired");
    console.log("inserting frame");
    document.body.append(frame);
    console.log("inserted frame");
    frame.onload = null;
    
    console.log("");
    
    const form = document.querySelector("form");
    form.onformdata = e => console.log("formdata event fired");
    console.log("submitting");
    form.submit();
    console.log("submitted");
    
    console.log("");
    
    // Doesn't fire in Chrome
    const dialog = document.querySelector("dialog");
    dialog.addEventListener("beforetoggle", e => console.log("beforetoggle event fired"));
    console.log("closing dialog");
    dialog.close();
    console.log("closed dialog");
    
    console.log("");
    
    onpopstate = e => console.log("popstate event fired");
    console.log("setting hash");
    location.hash = "foo";
    console.log("hash set");
    <form target=target><input required></form>
    <dialog open></dialog>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search