非同期JavaScriptが抱える「隠れた課題」を解く

TC39が提案するAsync Contextは、JavaScriptの非同期処理で値を暗黙的に伝播させるAPI群です。現在Stage 2の段階で、言語仕様への組み込みに向けて議論が進行しています。

背景として、JavaScriptの同期処理では「暗黙的な値の伝播」が自然に機能します。関数が共有変数を参照できたり、スコープ内でアクセス可能な値があれば、コード全体で一貫性が保たれます。しかし非同期処理(Promise・async/await)では話が変わります。

awaitを境に呼び出しスタックが置き換わり、元の文脈にアクセスできなくなる—これが非同期JavaScriptの根本的な課題です。

わかりやすい例を挙げると、synchronousなコードでは以下のように機能します:

function program() {
  const value = { key: 123 };
  try {
    shared = value;  // 共有変数に値をセット
    implicit();      // 関数内でsharedにアクセス可能
  } finally {
    shared = undefined;
  }
}
let shared;
function implicit() {
  console.log(shared.key);  // 123が出力される
}
program();

しかしこれを非同期にすると:

async function implicit() {
  console.log(shared.key);  // ここまではOK(123)
  await 1;  // awaitで実行が中断
  console.log(shared.key);  // エラー!sharedはundefinedになっている
}

async/awaitの登場により、コード見た目は同期処理に近くなりました。しかしその裏側では呼び出しスタックの置換が起きており、暗黙的な文脈が失われているのです。ユーザーランドコードだけでは解決できない構造的な問題といえます。

Async Contextが提供する3つのキー概念

この提案は以下の要素で非同期文脈の保持を実現します:

  • AsyncContext.Variable:非同期コード全体で共有される変数を定義・管理するAPI
  • AsyncContext.Snapshot:特定時点の文脈をスナップショットとして保存・復元する機能
  • Web API統合:Promise、fetch、setTimeoutなどのブラウザAPIに自動伝播

従来のアプローチ(手動でコンテキストを渡す)と比べ、開発者が明示的にパラメータ渡しをしなくても、値が自動的に非同期スコープ内に引き継がれるイメージです。

観点 従来(手動管理) Async Context
コンテキスト設定 関数パラメータで明示的に渡す AsyncContext.Variableで暗黙的に伝播
async/await境での保持 失われる可能性が高い 自動保持される
記述量 パラメータ増加で煩雑 シンプルに保たれる
実行時オーバーヘッド 最小 エンジン最適化に期待

この仕組みが実装されれば、エンジニアは「非同期境界を越えて値が保持される」ことを言語レベルで前提にできるようになります。

実務的なユースケースとフレームワークへの影響

Async Contextが活躍する場面は多岐にわたります。提案ドキュメントでは以下が明記されています:

  • 分散トレーシング:リクエストIDやトレースコンテキストを、マイクロサービス全体で保持
  • ロギング・モニタリング:ユーザーセッションIDをログ出力に自動埋め込み
  • リクエストスコープ:Express・Fastifyなどのフレームワークがリクエストごとのコンテキスト管理を簡素化
  • セキュリティ・認証:認証済みユーザー情報を非同期チェーン全体で利用可能に

個人的には、このアプローチはPythonのcontextvarsやGoのcontext.Contextに着想を得ているのだろうと感じます。言語を超えて「非同期処理での文脈伝播」という課題に共通解を模索する動きは、JavaScriptエコシステムの成熟度を示す良い指標ではないでしょうか。

Stage 2から標準化までのロード

現在Stage 2という段階は、提案の基本設計がTC39コミュニティで受け入れられ、API仕様の詳細調整フェーズにあることを意味します。提案リポジトリではMatrix roomでの隔週討議も行われており、実装者・フレームワーク開発者からのフィードバックが積極的に集められています。

Stage 3(候補段階)に進むには、仕様の安定性と実装可能性の実証が必要です。その後、複数のエンジン(V8・SpiderMonkey・JavaScriptCore)での実装を経て、Stage 4(標準化)に至る流れが一般的です。早くて数年単位での実装が期待できそうです。

ただし非同期スケジューリングやエラーハンドリングとの境界線については、まだ議論の余地があるとのこと。提案ドキュメントに「non-goals」として明記されていることから、スコープ絞りとのバランスを慎重に取っているのがわかります。