「もう、終わった処理を追いかけない」: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)`を差し込むところから始めてみてください。それだけで、あなたのアプリケーションの信頼性は確実に一段階上がります。
もし実装で詰まったら、いつでも戻ってきてください。また次の高度なテクニックでお会いしましょう。ハッピー・コーディング!