2020-08-15
|~4 min read
|656 words
Update:
AbortControlleris now shipping in Node, though currently under anexperimentalflag. The API is based on the web API described below. This is exciting as it brings the same capability to cancel Promises to the server!
With the introduction of the AbortController, we now have the ability to cancel fetch requests declaratively. This allows an early escape from a Promise which does not have its own method for canceling (i.e. there’s no Promise.cancel() to abort).
To make use of this, we’ll need a few pieces:
AbortController instancesignal property to the cancelable eventabort with the instance methodA bare bones example might look like the following. Let’s imagine we want to get information about Pikachu from the pokeapi:
fetch("https://pokeapi.co/api/v2/pokemon/pikachu", { signal }).then((res) =>
console.log(res),
)But then, we decide we actually want to abort that request:
const controller = new AbortController()
const { signal } = controller
fetch("https://pokeapi.co/api/v2/pokemon/pikachu", { signal })
.then((res) => console.log(res))
.catch((e) => console.log({ e, name: e.name }))
controller.abort()The resulting error’s name is AbortError with a message of "The user aborted a request."
This works because we’ve passed the signal into the fetch which acts as a listener for anytime an instance of the AbortController executes its abort method. The signal is an AbortSignal, an object that facilitates communication with a DOM request.
What about adding more control? Instead of a blanket abort, we can abort the fetch if too much time has passed using setTimeout to manage the execution. For example:
const fetchHandler = async (url, options) => {
const controller = new AbortController()
const { signal } = controller
const timeout = setTimeout(() => {
controller.abort()
}, 500)
try {
return await fetch(url, {
...options,
signal,
})
} catch (error) {
if (error.name === "AbortError") {
throw new Error(`Fetch canceled due to timeout`)
} else {
throw new Error(`Generic Error: ${error}`)
}
} finally {
clearTimeout(timeout)
}
}This example is technically more verbose than it needs to be, but I like it for its explicitness.1 This also builds on the same principles we saw earlier, except now the abort method is only called if 500 milliseconds elapse before we get to the finally block. If we get there then the timeout is cleared and the abort method will never execute.
While the AbortController brought the ability to cancel to the fetch API, we’ve had the ability cancel XHR requests for a while. The XMLHttpRequest has an abort method:
// source: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/abort
var xhr = new XMLHttpRequest(),
method = "GET",
url = "https://developer.mozilla.org/"
xhr.open(method, url, true)
xhr.send()
if (OH_NOES_WE_NEED_TO_CANCEL_RIGHT_NOW_OR_ELSE) {
xhr.abort()
}Additionally libraries that facilitate requests, like Axios, have made canceling ergonomic for a while, offering two solutions:
Canceling with a cancelToken
// source: https://github.com/axios/axios#cancellation
const CancelToken = axios.CancelToken
const source = CancelToken.source()
axios
.get("/user/12345", {
cancelToken: source.token,
})
.catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log("Request canceled", thrown.message)
} else {
// handle error
}
})
axios.post(
"/user/12345",
{
name: "new name",
},
{
cancelToken: source.token,
},
)
// cancel the request (the message parameter is optional)
source.cancel("Operation canceled by the user.")Cancel by passing an executor function to a CancelToken constructor
// source: https://github.com/axios/axios#cancellation
const CancelToken = axios.CancelToken
let cancel
axios.get("/user/12345", {
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c
}),
})
// cancel the request
cancel()Thank you to Jake Archibald, David Walsh, and MDN for their work in making cancelable fetch requests a reality (in the case of Jake) and understandable (all).
It’s ok to call .abort() after the fetch has already completed, fetch simply ignores it.
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!