Abort a web request
AbortController can be used along with the fetch api to abort a web request.
By creating an AbortController object, we could get a abort signal object. When the fetch request is initiated, we pass the abort signal object to the request's optionis object. Then we can call the abort function to abort the request at specified time.
const abortController = new AbortController();
fetch("https://yaox023.com/vocab/vite.svg", {
signal: abortController.signal
})
.then(response => response.text())
.then(text => console.log(text))
.catch(err => {
console.log(err);
// DOMException: The user aborted a request.
});
setTimeout(() => {
// abort after 10ms
abortController.abort();
}, 10);
Abort multiple fetch
We can use the same abort signal object to abort multiple request.
const abortController = new AbortController();
function request() {
fetch("https://yaox023.com/vocab/vite.svg", {
// use the same signal
signal: abortController.signal
})
.then(response => response.text())
.then(text => console.log(text))
.catch(err => {
console.log(err);
// DOMException: The user aborted a request.
});
}
request();
request();
request();
setTimeout(() => {
// abort after 10ms
abortController.abort();
}, 10);
Remove event listener
Normally, if we want to remove an event listener, we need to remember the callback's reference and pass it to the removeEventListener function.
const btn = document.getElementById("btn")
const handleClick = () => {
console.log("click");
btn.removeEventListener("click", handleClick)
};
btn.addEventListener("click", handleClick)
We can also achieve this by using abort signal.
const abortController = new AbortController();
const btn = document.getElementById("btn");
btn.addEventListener(
"click",
() => {
console.log("click");
abortController.abort();
},
{ signal: abortController.signal }
);
As you can see, in this way, we don't need to remember the callback reference, just call abort function is enough.
Abort api in node.js
In node.js, a lot of apis provide this abort signal option. For example, the readFile api in fs module, we can use this abort signal to abort the asynchronous file reading process.
const fs = require("fs").promises;
const abortController = new AbortController();
fs.readFile("./1.html", {
signal: abortController.signal,
encoding: "utf-8"
})
.then(text => console.log(text))
.catch(e => console.error(e));
// AbortError: The operation was aborted
setTimeout(() => {
abortController.abort();
}, 1);
Reason
We can pass the reason into the abort function, so later we can get the reason from abort error.
const fs = require("fs").promises;
const abortController = new AbortController();
fs.readFile("./1.html", {
signal: abortController.signal,
encoding: "utf-8"
})
.then(text => console.log(text))
.catch(e => console.error(e.cause));
// my abort reason
setTimeout(() => {
abortController.abort("my abort reason");
}, 0);
Timeout
In above example, we use setTimeout to abort the request. If we timeout value is fixed, we can use the static method timeout to create the signal so we don't need to abort manually.
const signal = AbortSignal.timeout(100);
fetch("https://yaox023.com/vocab/vite.svg", {
signal
})
.then(response => response.text())
.then(text => console.log(text))
.catch(err => {
console.log(err);
});
Abort event
The signal object has an abort event, which will be fired when the signal is aborted.
const signal = AbortSignal.timeout(100);
signal.addEventListener('abort', function (e) {
console.log('signal is aborted');
});
fetch("https://yaox023.com/vocab/vite.svg", {
signal
})
.then(response => response.text())
.then(text => console.log(text))
.catch(err => {
console.log(err);
});
The timeout function and this abort event features are very useful. I talked about some techinque to run js functions in background in Running JavaScript in "Background". This abort feature can be another technique to achieve the same effect.
function interval(cb, timeout) {
let stop = false;
function inner() {
const signal = AbortSignal.timeout(timeout);
signal.addEventListener('abort', () => {
cb();
if (!stop) {
inner();
}
});
}
inner();
return () => {
stop = true
}
}
const base = Date.now();
const cb = () => console.log(Date.now() - base);
const clear = interval(cb, 1000)
throwIfAborted
Just as its name shows, this function will check if the signal object is already aborted. If it is, then throw an error.
For example, if we want to poll for a condition, and we want to have control to stop this polling at certain checkpoint, we create a function as below. Code copied from this.
async function waitForCondition(func, targetValue, { signal } = {}) {
while (true) {
signal?.throwIfAborted();
const result = await func();
if (result === targetValue) {
return;
}
}
}