Aborting an event listener with AbortController
Did you know you can cancel an event listener with AbortController
?
Prelude: aborting a Fetch request
AbortController
is a relatively recent addition to the web platform. It is particulary useful used in conjunction with Fetch
.
To abort a Fetch
request, we can pass a signal to the request init object. The following code shows an example:
const start = document.getElementById("start");
const stop = document.getElementById("stop");
const controller = new AbortController();
const signal = controller.signal;
async function fetchData(url, requestInit) {
const response = await fetch(url, requestInit);
return await response.json();
}
start.addEventListener("click", async () => {
await fetchData("/some-url/", { signal });
});
stop.addEventListener("click", () => {
controller.abort();
});
Imagine that we have a "start" button and a "stop" button in the interface. If we click on "start", we initiate a Fetch
. While the XHR call is running, we can click on "stop" to cancel the request.
The ability to abort a request is useful in a lot of situations. For example, when it comes to returning large amount of data from the backend, we may want to give the user the ability to abort any ongoing request in case she loses interest in the response.
When we need to deal with relatively simple cancellation logic, and something like RxJS is not justified to be pulled in the dependencies list, resorting to plain, simple JavaScript can be a big win.
The nice thing is that AbortController
does work on addEventListener()
too. Let's see an example.
Aborting an event listener
Aborting an event listener works the same as aborting a Fetch
request:
- first we create a controller
- we extract the signal from the controller
- we pass the signal to the target we want to cancel, in this case an
addEventListener()
Here's a simple example to illustrate the concept:
const stop = document.getElementById("listen-no-longer");
const form = document.getElementById('my-form');
const controller = new AbortController();
const signal = controller.signal;
form.addEventListener('submit', (ev) => {
ev.preventDefault();
console.log('do stuff');
}, { signal });
stop.addEventListener("click", () => {
controller.abort();
});
Here's the HTML markup:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Aborting an event listener with AbortController</title>
</head>
<body>
<button id="listen-no-longer">listen no longer</button>
<form id="my-form">
<button type="submit">submit</button>
</form>
</body>
<script src="index.js"></script>
</html>
If we try this example in a page, we can see that submitting the form will print "do stuff" in the console, until we click the "listen no longer" button.
Once done that, the event listener will be cancelled, and our form will be free to go places, since there is nothing to intercept the submit
.
The example is deliberately simple to show the concept, but in real life I found this useful in all those situations where there is an event listener in place somewhere, and I can't change too much the piece of code which registered the listener.
But by changing a single line, I can pass an abort signal to addEventListener()
, and then call controller.abort()
from somewhere else in the codebase.
Hope you (re)learned something useful today! Thanks for reading!