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

ゾンビ化した非同期処理を叩き斬れ:AbortControllerで実現するNode.jsのリソース最適化設計

こんにちは。テックリードです。

皆さんのNode.jsアプリケーション、「離脱したユーザーのために、誰も見ていないレスポンスを生成し続けていないか?」と自問自答したことはありますか?

高負荷なAPIサーバーにおいて、最も見過ごされがちなメモリリークの温床は、メモリ不足や未解放のオブジェクトではなく、「既に不要になった非同期タスクの放置」です。クライアントがブラウザを閉じても、サーバー上のDBクエリや重い計算処理が動き続ける……この「ゾンビプロセス」を適切に処理することが、可用性を高めるための最後の砦となります。

本稿では、`AbortController` を軸に、Node.jsにおける非同期処理のキャンセル制御を「システムアーキテクチャの一部」として組み込む手法を伝授します。

—

1. なぜAbortControllerなのか:設計思想の核心

従来のNode.jsでは、`EventEmitter` や自前のフラグ管理でキャンセルを実装していましたが、これらは疎結合なモジュール間での受け渡しが困難でした。

`AbortController` は、Web標準(WHATWG)に準拠したインターフェースです。つまり、「HTTP層(Express/Fastify)」「ビジネスロジック」「データアクセス層(SQL/NoSQL)」の間で共通のキャンセルシグナルを伝搬できるという圧倒的な強みを持っています。

内部でのデータフロー

1. `AbortController` がインスタンス化される。
2. `AbortSignal` が各非同期関数へ「パスポート」のように渡される。
3. `AbortSignal` が発火(`abort()`)されると、イベントループ上のリスナーが即座に捕捉し、`AbortError` をスローしてPromiseの連鎖を強制終了する。

—

2. 実践:リクエスト中断のミドルウェア実装(Fastify版)

Fastifyはパフォーマンスを追求する現場のデファクトスタンダードです。これに `AbortController` を統合し、クライアント離脱を確実に捕捉します。

// middleware/abort-controller.js
// リクエスト終了を検知してAbortControllerを起動するミドルウェア
export const abortControllerMiddleware = (req, reply, done) => {
const controller = new AbortController();

// クライアントが切断した瞬間にコントローラーを起動
req.raw.on(‘close’, () => {
controller.abort(new Error(‘Client disconnected, request aborted.’));
});

// リクエストオブジェクトにシグナルを注入(下流へ伝搬させる)
req.signal = controller.signal;
done();
};

このミドルウェアを `fastify.addHook(‘onRequest’, …)` で登録するだけで、全エンドポイントが「離脱検知済み」になります。

—

3. DBクエリへの伝搬:Prisma/Knexでの活用

どれだけAPI層でキャンセルしても、DBへのクエリが飛んだままでは意味がありません。現代のORMは `signal` を受け取れるよう設計されています。

// services/user-service.js
export const getUserProfile = async (id, signal) => {
// prismaのfindUniqueに対してsignalを渡すことで、
// 中断時にコネクションプールを即座に解放する
return await prisma.user.findUnique({
where: { id },
signal // ここが重要。キャンセル信号がDBドライバまで到達する
});
};

実務の利益: これにより、低速なクエリが走っている最中にユーザーが離脱しても、即座にクエリがキャンセルされ、DBサーバー側のCPU/メモリ負荷が劇的に改善されます。

—

4. 開発環境を加速させる「プロの規律」

どれほど優れたアーキテクチャも、チーム全員が書かなければ意味がありません。生産性を底上げするツール設定を共有します。

ESLintによる強制力

`AbortSignal` を渡し忘れるミスを防ぐため、`eslint-plugin-sonarjs` 等で非同期関数への引数チェックを厳格化します。

// .eslintrc.json の設定例
{
“rules”: {
// 非同期関数には必ずsignal引数を含めることをチームの規約にする
“@typescript-eslint/naming-convention”: [
“error”,
{ “selector”: “parameter”, “format”: [“camelCase”], “filter”: “signal” }
]
}
}

必須VS Code拡張機能

1. Error Lens: `AbortError` が発生した際のスタックトレースを即座にコード行に表示し、デバッグ時間を短縮。
2. REST Client: `.http` ファイルで `AbortSignal` の挙動をシミュレート(接続タイムアウトや中断テスト)します。

神ショートカット:`Cmd + Shift + P` からの「リファクタリング」

関数定義に `AbortSignal` を追加する際、`Add parameter to function`(TypeScript対応拡張)を使用してください。手書きで引数を修正するのは時間の無駄です。

—

5. まとめ:アーキテクトからの提言

Node.jsにおける `AbortController` の実装は、単なる「エラーハンドリング」ではありません。それは、システム全体のリソース消費をユーザーの行動と同期させる「インテリジェントな呼吸」です。

1. ミドルウェアでシグナルを作成する
2. ビジネスロジックを通してDBまで渡す
3. 必要な箇所で`signal.throwIfAborted()`を呼び出す

このサイクルを標準化するだけで、皆さんのサーバーは「ゾンビ」に食い荒らされることなく、常に健全なパフォーマンスを維持できるようになります。

コードは「動く」だけでなく、「いかに美しく撤退できるか」で真価が問われます。ぜひ、今日からあなたのプロジェクトの非同期処理にこの「撤退戦略」を実装してみてください。

それでは、次回のコミットでまたお会いしましょう。Happy Hacking!

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