Error [ReferenceError: AbortController is not defined]

ReferenceError: AbortController is not defined:古い Node で AbortController が無いときの直し方

FIX SUMMARY verified
Applies when
node:14.16.1-alpineuse of global AbortController on Node < 15

Verified: reproduced in node:14.16.1-alpine, then the ReferenceError: AbortController is not defined signature was gone after the fix (exit 0).

タイムアウトやキャンセル処理で AbortController を使ったら、古い Node で次のエラーが出て落ちることがあります。

ReferenceError: AbortController is not defined
    at Object.<anonymous> (/app/index.js:1:20)

コピペ検索用の表記ゆれです。いずれも同じ原因です。

  • ReferenceError: AbortController is not defined
  • AbortController is not defined

原因は、グローバルの AbortController がフラグ無しの既定で使えるのが Node 15.0.0 からで、それより前の版では既定で AbortController が定義されていないことです。ブラウザや新しい Node には最初からあるので、開発マシンでは通ります。CI や本番のベースイメージが Node 14 のままだと、その環境でだけ AbortController is not defined になります。

これは典型的な版境界ですが、AbortController には見落としやすい点があります。公式ドキュメントは追加を Added in: v15.0.0, v14.17.0 と書いており、実装自体は 14.17.0 にバックポートされています。ただし 14 系では --experimental-abortcontroller フラグを付けたときだけグローバルに現れ、フラグ無しの既定ではどのパッチ(14.18・14.21 など)でも undefined のままです。だから「Node 14 のパッチを上げれば(何もせず)直る」ではなく、既定で使うなら 15 以降へ上げる必要があります。ここを取り違えると、14 系の中でパッチを上げて堂々巡りになります。

いつから AbortController が使えるのか

グローバルの AbortController(と対になる AbortSignal)は Node 15.0.0 でフラグ無しの既定になり、15.4.0 で安定扱いになりました。14.17.0 にも実験的に入っていますが、露出には起動フラグが要ります。

Node の版既定の AbortController実験フラグ(--experimental-abortcontroller
14.16.x 以前無しフラグでも不可
14.17.0 〜 14.x無しフラグを付ければ有効化できる
15.0.0 〜 15.3.x既定で有効(実験的)不要
15.4.0 以降既定で有効(安定)不要
16 以降有り不要

ドキュメントの Added in: v14.17.0 を見て「14 にあるはず」と思っても、フラグ無しでは現れないのがこの問題の落とし穴です(errfix の機械検証でも 14.17.0/14.17.6/14.18.0 はいずれもフラグ無しで undefined だった)。node:14 の固定イメージや 14 を保守用に残した CI では、パッチをいくら上げても既定では現れません。

再現(最小構成)

次の1ファイルを置きます。中断状態が signal.aborted に伝わるかを確かめる構成です。

// index.js
const controller = new AbortController();

controller.abort();
console.log(controller.signal.aborted);

これを Node 14 で node index.js すると ReferenceError: AbortController is not defined が出て、終了コードは 1 になります。Node 15 以降なら true と表示され、abort()signal.aborted に伝わっていることがわかります。

解決

1. Node を 15 以降(実務ではサポート中の LTS)に上げる

AbortController を既定で使い続けるなら、実行する Node(CI・本番の Docker ベースイメージを含む)を上げます。フラグ無しの既定になったのは 15.0.0 ですが、15・16 はすでに EOL(サポート終了)なので、本番ではその時点でサポート中の LTS へ揃えます。前述のとおり 14 系はパッチを上げても既定では現れないので、メジャー版をまたぐ更新になります。他の依存パッケージが新しい版に対応しているかを併せて確認してください。

2. ポリフィルを入れる(古い Node を残す場合)

Node 14 をサポート対象に残すなら、AbortController 相当を提供するパッケージを使います。node-abort-controller が広く使われており、AbortController / AbortSignal を補います(起動フラグを足したくない場合の選択肢)。

// index.js(ポリフィルを使う)
const { AbortController } = require('node-abort-controller');

const controller = new AbortController();
controller.abort();
console.log(controller.signal.aborted);

グローバルの AbortController を前提に書かれた別のコードにも通したいときは、globalThis.AbortController へ代入して橋渡しできます。

const { AbortController } = require('node-abort-controller');

if (typeof globalThis.AbortController !== 'function') {
  globalThis.AbortController = AbortController;
}

切り分け(似ているが原因が違うもの)

  • AbortController は使えるが、渡した先が signal を無視するAbortController 自体が使えても、それを受け取る API 側が中断に対応していないと止まりません。signal を受け取り始めた版は API ごとに違い、たとえば fs.readFile は 15.2.0、http.request は 15.3.0、fs.watch は 15.9.0 と別々です(いずれも 14.17.0 へバックポート済み)。「一括で何版から」とは言えないので、「AbortController は動くのに処理が止まらない」ときは、渡す相手の API がその版で signal を見るかを個別に確認してください。なお 14.17.0 では、これらの API が signal を受け取れる一方でグローバル AbortController はフラグ無しだと無い、という食い違いも起こります。
  • 同じ版境界で別のグローバル API が落ちるAbortController と同じく「新しい Node に組み込まれたグローバル」は、古い版で is not defined になります。fetch(Node 18+)、structuredClone(17+)、crypto.randomUUID(14.17/16+、メソッドなので is not a function)などが該当します。原因は共通で、対処も Node を上げるかポリフィルを入れるかの二択です。fetchReferenceError: fetch is not defined の直し方structuredCloneReferenceError: structuredClone is not defined の直し方crypto.randomUUIDTypeError: crypto.randomUUID is not a function の直し方 を参照してください。
  • ブラウザ側で AbortController is not defined が出た:ブラウザの AbortController は主要ブラウザで広く使えます。古いブラウザや一部の実行環境で無い場合は、Node と同じくポリフィルで補います。この記事は Node 実行時の話で、スタックトレースにファイルパスや node:internal/... が出ているなら Node 側の問題です。

検証(machine-verified)

この修正は node:14.16.1-alpine のバージョン固定コンテナ内で再現し、修正後に ReferenceError: AbortController is not defined のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs node/abortcontroller-not-defined
● reproduce ReferenceError: AbortController is not defined present ✓
● apply fix exit 0
● re-run ReferenceError: AbortController is not defined gone ✓
PASS verified · node:14.16.1-alpine · signature gone