目次を表示する

Async Local Storage Deep Dive 2026 ─ 内部実装・落とし穴・性能を 9 章で読み解く

これから ─ TC39 AsyncContext と ALS の今後

これから ─ TC39 AsyncContext と ALS の今後

Node.js の ALS は、第 5 章で見たように AsyncContextFrame という独自実装にたどり着いた。だがこの方向は Node だけのものではない。ECMAScript 自体に AsyncContext という標準提案がある。

最終章では JS 言語標準と他ランタイムの動向を見ながら、書き手として今知っておくべき takeaway をまとめる。

TC39 AsyncContext proposal

TC39 AsyncContext は、Node.js の ALS に相当する仕組みを 言語標準として ECMAScript に取り込む提案。Stage 3 で進行中(2026 年時点)。

API は ALS と非常に近い:

// 提案中の API
const ctx = new AsyncContext.Variable<{ requestId: string }>();

ctx.run({ requestId: 'r-1' }, async () => {
  await something();
  ctx.get(); // { requestId: 'r-1' }
});

ctx.get(); // undefined(スコープ外)

run / get のシグネチャは ALS の run / getStore とほぼ同じ。Node の ALS が 語の選び方を含めて 言語標準に近づいていったことが分かる(Node 24 の AsyncContextFrame という命名はこの提案の文脈に揃えたものでもある)。

提案の重要な点:

  1. ブラウザでも動く: 現在 zone.js などで擬似実装されている領域が、標準に統合される
  2. get のみ提供(enterWith なし): 第 1 章で触れた enterWith の罠を仕様レベルで排除
  3. Snapshot API: 関数を「現在の context」に bind する仕組みを標準化(ALS の AsyncResource.bind に相当)

Bun / Deno の動向

Bun

Bun は ALS をネイティブ実装している(oven-sh/bun#24324)。Linked AsyncContextFrame ─ Node 24 の AsyncContextFrame に着想を得た同等構造 ─ を実装する issue が進行中。Bun は性能特性を売りにするランタイムだから、ALS の overhead を Node と同等以下に抑える努力が続いている。

Deno

Deno は Web 標準互換を強く意識するため、TC39 AsyncContext proposal を直接の実装ターゲットにしている。Node 互換レイヤーとしての ALS API も提供されているが、本命は標準提案の方向。

つまり、収斂しつつある

ここまでの状況を見ると:

  • Node.js: ALS が AsyncContextFrame で TC39 風の API に内部的に揃った
  • Bun: 同等構造を実装中
  • Deno: TC39 proposal を直接ターゲット
  • ブラウザ: TC39 proposal を待っている

Promise の自動継承を embedder data に乗せる、というアーキテクチャは 3 ランタイム + 言語標準が収斂しつつある。第 4-5 章で見た V8 の仕組みは、Node 固有の最適化ではなく JS エンジン全体の進む先だった、と振り返れる。

ALS を使うときに今知っておくべきこと

このシリーズで扱った内容を、実用 takeaway として整理する。

1. 基本は runenterWith は最後の手段

第 1 章。enterWith のスコープなし伝播は便利だが、リクエスト間の漏洩リスクが高い。run を基本とし、enterWith は本当に必要な時だけ。TC39 proposal でも enterWith 相当はない。標準の方向に揃えるのが安全

2. context loss は「Promise を経由しない非同期」で起きる

第 6 章。callback / 独自 thenable / EventEmitter / Worker thread。標準 Promise の経路に戻すか、AsyncResource で手動キャプチャするのが解。

3. Node のバージョンで内部が違うことを意識する

第 3-5 章。同じ ALS API でも、Node 13 / 16 / 24 で内部実装は別物。性能特性も違う。Node 24+ なら基本的に気にしなくていいが、20.x 系の長期メンテで動かすシステムは旧実装の特性を意識する必要がある。

4. --no-async-context-frame は使わない

第 5 章。旧実装にフォールバックするフラグだが、性能上のメリットはなく、第 8 章の脆弱性パッチを当てない場合にリスクが増えるだけ。互換性問題に直面した場合の最後の手段。

5. APM / OpenTelemetry を使うなら overhead を測る

第 7-8 章。ALS 単独は ~10% だが、createHook 直叩きの APM は workload によって 80% 落ちる。サンプリングを設定する、選択的計装にする、などの運用設計が必要。

6. 自分のフレームワークが createHook を使っているかは知っておく

第 8 章。React / Next.js は使わない。多くの APM は使う。これがセキュリティとパフォーマンスの両面でクリティカルな分岐になる ── 例外的な脆弱性が出た時、自分のスタックが影響圏内か即答できる必要がある。

終わりに

このシリーズでは ALS の API → 内部実装 → 落とし穴 → 性能 → 脆弱性 → 言語標準、という 9 段の階段を縦に降りた。

途中で何度か出した「なぜ動くのか」の答えは、突き詰めると 1 行になる:

V8 が標準 Promise の continuation に対して、Embedder(Node.js)が指定した値を自動継承する。

第 1 章で投げた素朴な疑問の答えがこれだった。残りの章は全部、この 1 行の周辺の 歴史 / 限界 / 進化 / 失敗 / 復元 / 未来 だ。

ここから先は読者の現場で、ALS を「仕組みを知って使う道具」に格上げしてほしい。それがこのシリーズのゴールだった。

参考文献・情報源

公式ドキュメント

Pull Request / Issue

TC39

ベンチマーク・解説

他ランタイム