Search code examples
javascriptaddeventlistenercallstackremoveeventlistener

Why does console.trace() show a growing stack trace when using event listeners with { once: true } option?


I have this inline js code.

let btn = document.querySelector("button");
btn.addEventListener("click", foo, { once: true });

function foo() {
  btn.addEventListener("click", bar, { once: true });
  console.log("Foo called");
  console.trace();
}

function bar() {
  btn.addEventListener("click", buz, { once: true });
  console.log("Bar called");
  console.trace();
}

function buz() {
  btn.addEventListener("click", foo, { once: true });
  console.log("Buz called");
  console.trace();
}
<button>Click</button>

The output I received was a bit difficult to understand. After few button clicks, it shows something like this.

Foo called index.html:11:17
foo file:///.../index.html:12
    (Async: EventListener.handleEvent)

Bar called index.html:16:17
bar file:///.../index.html:17
    (Async: EventListener.handleEvent)
foo file:///.../index.html:10
    (Async: EventListener.handleEvent)

Buz called index.html:21:17
buz file:///.../index.html:22
    (Async: EventListener.handleEvent)
bar file:///.../index.html:15
    (Async: EventListener.handleEvent)
foo file:///.../index.html:10
    (Async: EventListener.handleEvent)

As you can see, at the "Bar called index.html:16:17" line there seems to be two stack frames. And at the "Buz called" there are three stack frames. So my question is why this happen?

I expected it to produce something like this.

Foo called index.html:11:17
foo file:///.../index.html:12
    (Async: EventListener.handleEvent)

Bar called index.html:16:17
bar file:///.../index.html:17
    (Async: EventListener.handleEvent)

Buz called index.html:21:17
buz file:///.../index.html:22
    (Async: EventListener.handleEvent)

Solution

  • There's this note at the top of the documentation page:

    Note: In some browsers, console.trace() may also output the sequence of calls and asynchronous events leading to the current console.trace() which are not on the call stack — to help identify the origin of the current event evaluation loop.

    console.trace() | MDN

    This is not a bug of either Chrome or Firefox, since the specs made it clear that the trace is implementation-specific:

    1.1.8. trace(...data)

    1. Let trace be some implementation-specific, potentially-interactive representation of the callstack from where this function was called.
    2. Optionally, let formattedData be the result of Formatter(data), and incorporate formattedData as a label for trace.
    3. Perform Printer("trace", « trace »).

    Console Standard | WHATWG