About this article
This article was generated using an automated workflow powered by generative AI. After reviewing the MDN Web Docs and the Node.js AbortController/fetch specifications, we created a localhost experiment to compare aborted and successfully completed requests without relying on external APIs. Operation on an actual Node.js hardware environment has not yet been verified.
Verification Status: 📘 MDN / Node.js official specifications verified / Hardware execution unverified
When calling an API using JavaScript'sfetch(), the destination server may fail to respond, causing the UI to hang. This is whereAbortControllercomes in. It allows you to abort requests after a certain period of time or stop processing when a user clicks a cancel button.
However,aborting a network communication is different from canceling operations on the server side. For instance, if you close the screen after sending an order API request, the order processing on the server might have already succeeded. In actual business logic, duplicate prevention and result verification are required separately.
How AbortController works
AbortControllerincludes ansignalobject. By passing its signal to fetch and later callingabort(), any pending fetch request is aborted.
const controller = new AbortController();
const responsePromise = fetch("/api/data", {
signal: controller.signal
});
// 必要になったときに中断する
controller.abort();
This is a conceptual example and/api/datais not intended to be copy-pasted directly into pages where dependencies are missing to run out of the box. For a side-by-side comparison, we will set up a small local server using Node.js next.
Localhost demo you can try right away
Requires Node.js 18 or higher. Set the filename toabort-demo.jsSave as.
const http = require("node:http");
async function main() {
const server = http.createServer((_req, res) => {
// サーバー側は400ミリ秒かけて応答する
setTimeout(() => {
if (!res.writableEnded) {
res.writeHead(200, {"content-type": "text/plain"});
res.end("finished");
}
}, 400);
});
// 外部へ公開しないよう localhost のみに待ち受ける
server.listen(0, "127.0.0.1");
await new Promise(resolve => server.once("listening", resolve));
const url = `http://127.0.0.1:${server.address().port}/`;
try {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 100);
try {
await fetch(url, {signal: controller.signal});
console.log("Unexpectedly completed before abort");
process.exitCode = 1;
} catch (error) {
if (error.name !== "AbortError") throw error;
console.log("First request: AbortError (cancelled by client)");
} finally {
clearTimeout(timer);
}
// 同じサーバーへ今度は中断せずにアクセスする
const response = await fetch(url);
console.log(`Second request: ${response.status} ${await response.text()}`);
} finally {
server.closeAllConnections?.();
await new Promise(resolve => server.close(resolve));
}
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});
server.listen(0)specifies that the OS should select an available port. Because the port number is not fixed, conflicts with other local services are minimized.
node abort-demo.js
The expected results are as follows. These are based on the API specification rather than physical device logs.
First request: AbortError (cancelled by client) Second request: 200 finished
This experiment makes the server wait for 400ms, aborts only the first client at 100ms, and allows the second client to fetch until completion without aborting. Because it compares two accesses to the same local server, there is no need to send actual network traffic externally.
GitHub Sample: AbortController and localhost fetchThe code and execution instructions are stored in.
Why stop the timer in finally?
setTimeoutIf remains, may be executed later for a fetch that has already completed. Unnecessary timers are cleared inabort()regardless of success or failure usingclearTimeout.
The sample also stops the server. This prevents the server from continuing to run in the background when you copy and test the article's code multiple times.
Change one place to see the difference
100Change to600before trying.
// 変更前 const timer = setTimeout(() => controller.abort(), 100); // 変更後 const timer = setTimeout(() => controller.abort(), 600);
Since the server response is approximately 400ms, setting it to 600ms may cause the fetch to complete before the abort, resulting in the message "Unexpectedly completed before abort" in the sample. This codedoes not overlook the failure to abortand sets the exit code to failure. Because actual timing varies depending on OS load, please also measure actual response times.
Difference from AbortSignal.timeout
Modern JavaScript environments also includeAbortSignal.timeout(1000).
const response = await fetch("/api/data", {
signal: AbortSignal.timeout(1000)
});
This example is also used in an environment where the target API exists. When a certain time is exceeded, AbortSignal.timeout normallyTimeoutErrorreturns an exception. On the other hand, if you explicitlycontroller.abort()call, it typically becomesAbortError.
Please check the supported versions for browsers and Node.js at the time of publication. Especially in enterprise applications requiring compatibility, older environments may also be targeted.
How to handle errors
| State | General handling | Next check |
|---|---|---|
| AbortError | Interruption due to user operation or similar | Display the reason for operation cancellation |
| TimeoutError | Interruption due to elapsed time | Response delay and retry policy |
| TypeError, etc. | Network/other fetch failure | Communication destination, permissions, CORS, network |
| HTTP 500 | Fetch itself may resolve | Determine using response.ok / status |
What is particularly important is thatHTTP 500 does not necessarily reject the fetch Promise. If the request is received and an HTTP response is obtained, HTTP errors areresponse.statusdetermined using . Therefore, check as follows.
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error(`HTTP status: ${response.status}`);
}
When used in production
Retrying a data read API carries a different level of risk than retrying a mutation API such as an order or money transfer. Just because the client aborts the request does not necessarily mean the processing on the server has been canceled.
Re-executing mutation APIs requires design considerations such as idempotency keys, transaction IDs, result verification, and duplicate prevention.AbortController is a mechanism for canceling network communication, not a business transaction cancellation feature.
