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 declarationsfunction scrollHandler() { console.log('window was scrolled');}
function resizeHandler() { console.log('window was resized');}
// passing the existing callback functions into addEventListenerwindow.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 removedwindow.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 callbackwindow.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 AbortControllercontroller.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 AbortControllercontroller.abort();
Additional reference: MDN addEventListener documentation