【入門編】Node.jsにおける「AbortController」による非同期処理のキャンセル制御:リクエスト中断のベストプラクティス – 実行環境・ランタイム・コンパイラ生産性向上バイブル

「もう、終わった処理を追いかけない」:Node.jsで非同期処理をスマートに手放すAbortControllerの極意

こんにちは。開発環境アーキテクトです。

現場でコードをレビューしていると、非常に残念な光景を目にします。「ユーザーがもう画面を閉じたのに、裏側で一生懸命DBにクエリを投げ続け、重たいJSONを生成し続けているNode.jsプロセス」です。

これは単なるリソースの浪費ではありません。メモリを圧迫し、CPUを枯渇させ、最終的にはサーバー全体のパフォーマンスをジワジワと殺す「見えない負債」です。

今日は、Node.jsの現代的な非同期制御の要である`AbortController`を使って、この負債を根絶する方法を伝授します。これをマスターすれば、あなたの書くコードの「立ち居振る舞い」は、一段とプロフェッショナルなものになりますよ。

—

1. なぜ「キャンセル」が必要なのか?

Node.jsの非同期処理は強力ですが、一方で「一度走り出した非同期関数は、完了するかエラーになるまで止まらない」という性質を持っています。

例えば、Reactのコンポーネントがアンマウントされた後や、Expressのクライアントがコネクションを切断した後に、その結果を待つ意味はありますか?ありませんよね。`AbortController`は、この「終わる必要のない処理」に対して、「もういいよ」と中断シグナルを送るための標準APIです。

2. AbortControllerの基本構造

`AbortController`は、以下の2つのパーツで構成されます。

  • Controller: キャンセルを実行するための司令塔。
  • Signal: 処理側に渡される「キャンセル状態」を監視する通知役。

まずは、これがどう動くのか、最小構成のコードを見てみましょう。

// 1. コントローラーを生成
const controller = new AbortController();
const { signal } = controller;

// 2. キャンセルされた時に実行するリスナー
signal.addEventListener(‘abort’, () => {
console.log(‘【通知】処理が中断されました!リソースを解放します。’);
});

// 3. 実行中の処理(fetchを想定)
// signalをオプションとして渡すのがポイントです
fetch(‘https://api.example.com/data’, { signal })
.then(res => res.json())
.catch(err => {
if (err.name === ‘AbortError’) {
console.log(‘【制御】意図的な中断により終了しました。’);
}
});

// 4. 任意のタイミングで中断を命じる
controller.abort();

これだけで、ネットワークリクエストは即座に切断されます。ソケットは閉じられ、サーバーの負荷は即座に軽減されます。

—

3. 実践:タイムアウト付きの安全なリクエスト

「処理が長すぎる場合に自動で打ち切る」という要件は、APIゲートウェイを作る際に不可欠です。`AbortSignal.timeout()`を使うと、コードが格段に美しくなります。

async function fetchWithTimeout(url, ms) {
// 指定ミリ秒後に自動的にabortするシグナルを生成
const signal = AbortSignal.timeout(ms);

try {
const response = await fetch(url, { signal });
return await response.json();
} catch (err) {
if (err.name === ‘TimeoutError’) {
console.error(`エラー:${ms}ms以内に応答がありませんでした。`);
}
throw err;
}
}

—

4. Webフレームワークとの統合(Expressの例)

これが最も現場で役立つテクニックです。Expressなどのフレームワークでは、クライアントがブラウザを閉じた瞬間にリクエストが切れます。この「切断」を`req.on(‘close’)`で検知し、`AbortSignal`に流し込むのです。

const express = require(‘express’);
const app = express();

app.get(‘/heavy-process’, async (req, res) => {
const controller = new AbortController();

// クライアントが切断したら、controllerに中断を指示
req.on(‘close’, () => {
controller.abort();
console.log(‘クライアントが離脱しました。処理を停止します。’);
});

try {
// 重たい処理(signalを渡せるDBドライバやfetch等)
const result = await someLongRunningTask(controller.signal);
res.json(result);
} catch (err) {
if (err.name === ‘AbortError’) {
// ログ出力のみでOK、レスポンスを返す必要はない
return;
}
res.status(500).send(‘Server Error’);
}
});

なぜこれが重要なのか?

このパターンを導入すると、あなたのバックエンドは「ユーザーの離脱」というイベントに対して、物理的に「無駄な計算をしない」という最適化を自動的に行うようになります。

初心者のうちは「動けばいい」と考えがちですが、中級者へのステップアップは「いかに無駄を削ぎ落とすか」にあります。`AbortController`は、Node.js標準のAPIであるため、ライブラリを追加する必要もなく、今日からすぐに使えます。

最後に:アーキテクトからの助言

「非同期処理を制御下に置く」ということは、プログラムを「書きっぱなし」から「管理するもの」へと進化させる第一歩です。

まずは、今書いているコードの中で「fetch」を使っている箇所に、`AbortSignal.timeout(5000)`を差し込むところから始めてみてください。それだけで、あなたのアプリケーションの信頼性は確実に一段階上がります。

もし実装で詰まったら、いつでも戻ってきてください。また次の高度なテクニックでお会いしましょう。ハッピー・コーディング!

タイトルとURLをコピーしました