DF[dot]DEV

Remove event listeners using an AbortController

Full credit to the Bytes.dev newsletter for showcasing this in a recent issue. I appreciate them bringing up this fantastic recent addition to browsers that went under the radar for me.

Traditionally if we needed to use removeEventListener, we would need to save a reference to the callback used by addEventListener, meaning that creating functions inside the addEventListener call wouldn’t be an option. Since functions are compared by reference in JavaScript, we would need to create these callbacks as either function declarations or as function expressions so they could be compared correctly when calling removeEventListener.

const controller = new AbortController();
// creating callback functions via function declarations
function scrollHandler() {
console.log('window was scrolled');
}
function resizeHandler() {
console.log('window was resized');
}
// passing the existing callback functions into addEventListener
window.addEventListener('scroll', scrollHandler);
window.addEventListener('resize', resizeHandler);
// ...some time later when it's time to cleanup your listeners
// referencing the same callback functions so they are correctly removed
window.removeEventListener('scroll', scrollHandler);
window.removeEventListener('resize', resizeHandler);

Starting in early to mid 2021, browsers began supporting a signal property in the options object of addEventListener. This allows us to pass in the signal property of an AbortController and can then use the abort method of our AbortController to remove all event listener callbacks associated with the controller.

const controller = new AbortController();
// creating anonymous functions inside the addEventListener callback
window.addEventListener('scroll', () => console.log('window was scrolled'), {
signal: controller.signal
});
window.addEventListener('resize', () => console.log('window was resized'), {
signal: controller.signal
});
// ...some time later when it's time to cleanup your listeners
// this removes all event listeners associated with this AbortController
controller.abort();

I believe this results in easier to read code, especially when the callback function is only used to handle a single event type. Additionally, the callback handler code is now visually associated with the event.

With the AbortController method of removing listeners you can also have the best of both worlds. If your callback function is more than a few lines or is needed by more than one event listener, you can still create it separately and pass it to addEventListener as you would have previously. As long as your AbortController signal is included in the addEventListener call, all listener callbacks regardless of where they are created will be removed.

const controller = new AbortController();
function complexCallback() {
// Let's pretend this callback handles a lot of logic
// and it doesn't make sense to create it during the
// 'addEventListener' call
}
window.addEventListener('scroll', () => console.log('window was scrolled'), {
signal: controller.signal
});
window.addEventListener('resize', complexCallback, {
signal: controller.signal
});
// removes all event listeners associated with this AbortController
controller.abort();

Additional reference: MDN addEventListener documentation

Back to posts