AbortController in Shopify Themes — Remove All Event Listeners With One Call
Cleaning up event listeners in JavaScript traditionally meant storing bound function references and calling removeEventListener for each one individually in disconnectedCallback. For a component with five event listeners and two fetch calls, that is seven cleanup lines that developers routinely forget or get wrong. AbortController solves this elegantly: pass its signal to addEventListener and fetch, then call abort() once in disconnectedCallback to cancel everything simultaneously. This is now the standard pattern in Shopify themes.
How AbortController works as a cleanup mechanism
AbortController has two parts: the controller (which you call abort() on) and its signal (which you pass to operations you want to be cancellable). When you call controller.abort(), it fires an 'abort' event on the signal and marks it as aborted. The addEventListener option {signal} removes the listener when the signal fires. The fetch option {signal} cancels the pending network request when the signal fires. One abort() call cleans up everything registered with that signal.
Using signal with addEventListener
The signal option for addEventListener was standardized in 2021 and is supported in all modern browsers. Pattern: const controller = new AbortController(); element.addEventListener('click', handler, { signal: controller.signal }); window.addEventListener('scroll', scrollHandler, { signal: controller.signal }); document.addEventListener('keydown', keyHandler, { signal: controller.signal }). When you call controller.abort(), all three listeners are removed automatically. No need to store handler references or call removeEventListener individually.
Using signal with fetch for request cancellation
Pass the signal to fetch to make requests cancellable: const response = await fetch('/cart.js', { signal: controller.signal }). If controller.abort() is called while the request is pending, the fetch throws an AbortError. Always handle this in your error handling: catch(err => { if (err.name === 'AbortError') return; // Expected — component was unmounted. console.error(err); }). This prevents error logs from expected cancellations when components unmount during pending requests.
Pattern for Shopify Web Components
The complete pattern: class MyComponent extends HTMLElement { connectedCallback() { this.abortController = new AbortController(); const { signal } = this.abortController; window.addEventListener('resize', this.onResize, { signal }); this.loadData(signal); } async loadData(signal) { try { const data = await fetch('/cart.js', { signal }).then(r => r.json()); this.render(data); } catch(e) { if (e.name !== 'AbortError') throw e; } } disconnectedCallback() { this.abortController.abort(); } }
The fix: before and after
// CODE_COMPARISON
Frequently asked questions
- Is the signal option for addEventListener supported in all browsers?
The signal option for addEventListener is supported in Chrome 90+, Firefox 88+, Safari 15+, and Edge 90+. This covers all browser versions released since mid-2021. For Shopify themes targeting modern browsers, this is safe to use without a polyfill. IE11 is not supported, but Shopify dropped IE11 support in 2022.
- Can I reuse an AbortController after calling abort()?
No. Once abort() is called, the controller's signal is permanently aborted. Any subsequent addEventListener calls with that signal are immediately removed. Create a new AbortController in connectedCallback each time the component is connected — this handles the case where a component is disconnected and reconnected (which can happen during Theme Editor interactions).
- What happens to the fetch request when abort() is called?
The fetch is immediately cancelled at the network level if still pending — no response is processed. If the fetch has already completed and you are processing the response, aborting the controller does not affect the already-received data. The AbortError is thrown inside the fetch Promise chain, so handle it in your catch block by checking err.name !== 'AbortError' before re-throwing.
// SCAN_YOUR_CODE
Does your theme have this bug?
Paste your code. Syphio automatically detects and fixes this error and hundreds of others — in seconds.
Check my JavaScript →