Context:
When executing callbacks that were added with addEventListener, the browser seemingly has a way of executing the callbacks in order, and synchronously triggering an uncaught error if one fails, without disrupting/preventing the subsequent handlers from being executed.
For example:
let button = document.createElement("button");
button.textContent = 'Click';
document.body.append(button);
button.addEventListener('click', () => {
console.log('1st listener');
throw new Error('error');
});
button.addEventListener('click', () => {
console.log('2nd listener');
});
console.log("Before click");
button.click();
console.log("After click");
shows these logs:
Before click
1st listener
Uncaught Error: error
2nd listener
After click
The key point here is that the error occurs between the two listeners.
Question:
I’m wondering how to emulate this behavior with my own addEventListener-like framework. Here’s some example code to demonstrate what I’m trying to achieve:
let callbacks = [
() => { console.log('1st listener'); throw new Error("error"); },
() => { console.log('2nd listener') },
];
function triggerHandlers() {
for(let cb of callbacks) {
try {
cb();
} catch(e) {
console.error(e); // how to *synchronously* trigger an uncaught error here without interrupting the for loop?
}
}
}
console.log("Before click");
triggerHandlers();
console.log("After click");
Possible Solutions:
- Maybe using a
finallyblock which recursively calls the function, instead of aforloop? This seems a bit hacky though. I’m hoping there’s a more straightforward approach that doesn’t require recursion. - Using the built-in
EventTargetto side-step the problem. This isn’t really a viable solution in my case, except perhaps as a “hacky” solution that uses it “surgically” within the codebase to emulate the above behavior (i.e. not using it in the way it’s supposed to be used).
Non-Solutions:
- Rethrowing the error via something like
setTimeout(()=>throw e, 0)(or equivalent) does not work, since it’s not synchronous.