There\'s a difference in the execution order of the microtask/task queues when a button is clicked in the DOM, vs it being programatically clicked.
Fascinating question.
First, the easy part: When you call click
, it's a synchronous call triggering all of the event handlers on the button. You can see that if you add logging around the call:
const btn = document.querySelector('#btn');
btn.addEventListener("click", function() {
Promise.resolve().then(function() { console.log('resolved-1'); });
console.log('click-1');
});
btn.addEventListener("click", function() {
Promise.resolve().then(function() { console.log('resolved-2'); });
console.log('click-2');
});
document.getElementById("btn-simulate").addEventListener("click", function() {
console.log("About to call click");
btn.click();
console.log("Done calling click");
});
Since the handlers are run synchronously, microtasks are processed only after both handlers have finished. Processing them sooner would require breaking JavaScript's run-to-completion semantics.
In contrast, when the event is dispatched via the DOM, it's more interesting: Each handler is invoked. Invoking a handler includes cleaning up after running script, which includes doing a microtask checkpoint, running any pending microtasks. So microtasks scheduled by the handler that was invoked get run before the next handler gets run.
That's "why" they're different in one sense: Because the handler callbacks are called synchronously, in order, when you use click()
, and so there's no opportunity to process microtasks between them.
Looking at "why" slightly differently: Why are the handlers called synchronously when you use click()
? Primarily because of history, that's what early browsers did and so it can't be changed. But they're also synchronous if you use dispatchEvent
:
const e = new MouseEvent("click");
btn.dispatchEvent(e);
In that case, the handlers are still run synchronously, because the code using it might need to look at e
to see if the default action was prevented or similar. (It could have been defined differently, providing a callback or some such for when the event was done being dispatched, but it wasn't. I'd guess that it wasn't for either simplicity, compatibility with click
, or both.)