Aborting fetch in JavaScript with AbortController – Request timeout and cleanup

プログラミング・Web開発カテゴリを表すパンダのイラスト Programming / Web Development

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

StateGeneral handlingNext check
AbortErrorInterruption due to user operation or similarDisplay the reason for operation cancellation
TimeoutErrorInterruption due to elapsed timeResponse delay and retry policy
TypeError, etc.Network/other fetch failureCommunication destination, permissions, CORS, network
HTTP 500Fetch itself may resolveDetermine 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.

Official documentation

Document information

Article title
Aborting fetch in JavaScript with AbortController – Request timeout and cleanup
Published
Updated
Source
https://papanda925.com/?p=18103&lang=en

License: Text and original figures for which this site holds the relevant rights are available under CC BY 4.0 , unless otherwise noted. This article may include content created or edited with generative AI. If code has a separate license notice or a linked GitHub repository license, that license takes precedence for the code. Quotations, third-party materials, images, and trademarks are excluded from this license. Usage policy

Copied title and URL