【テクニカル・上級編】 Promiseオブジェクトの型判定 – JavaScript実践ガイド

Promiseの「正体」を見極める:Thenable地獄からの脱却とアーキテクチャの防衛術

JavaScriptの非同期処理において、`instanceof Promise` を信じ切っている諸君は、一度立ち止まって考えてみてほしい。我々が扱うのは、ただの自作Promiseだけではない。ライブラリが返す謎のオブジェクト、ブラウザのネーティブ実装、さらには別ウィンドウのコンテキストで生成されたPromiseまで、多種多様な「非同期の亡霊」たちが、君たちの堅牢なアーキテクチャの隙間を虎視眈々と狙っている。

今日は、表面的な `typeof` の話ではなく、プロダクション環境で「本当に信頼できる」Promiseの判定ロジックについて、V8エンジンの裏側を覗き見るような話をしよう。

—

なぜ `instanceof Promise` が「裏切り」を生むのか

まず、基本の確認だ。`instanceof` はプロトタイプチェーンを辿る。しかし、現代のWebアプリケーションにおいて、これは致命的な欠陥を抱えている。

1. クロスコンテキスト(iframe等)の壁: 別ウィンドウやiframe内で定義された `Promise` コンストラクタは、親ウィンドウの `Promise` とは別物だ。`instanceof` はプロトタイプチェーンを比較するため、ここであっさりと `false` を返す。
2. ポリフィルの罠: 古い環境や特定のライブラリが独自に組み込んだPromiseポリフィルは、標準の `Promise` クラスを継承していない場合がある。

これらを無視して「俺の書いたコードはES6環境だから大丈夫」と高を括るのは、戦場で銃の整備を怠るのと同じだ。

Thenable:非同期の「なりすまし」をどう裁くか

Promiseの厳密な判定において避けて通れないのが「Thenable(ゼナブル)」だ。`then` メソッドを持つオブジェクトは、Promiseのフリをして非同期処理のパイプラインに紛れ込む。

/

  • 厳密なThenable判定のアーキテクチャ
  • 単に “then” というプロパティがあるか確認するだけでは不十分。
  • それが「関数」であり、呼び出し可能であることを保証しなければならない。

/
function isThenable(value) {
// null/undefinedを除外し、オブジェクトまたは関数であることを確認
if (value !== null && (typeof value === ‘object’ || typeof value === ‘function’)) {
// thenが関数であるかを確認。これはPromise/A+仕様の根幹
return typeof value.then === ‘function’;
}
return false;
}

この判定は非常に安価だ。メモリを確保せず、プロトタイプチェーンを走査せず、単なるプロパティアクセスのみで完了する。レンダリングループや高頻度で実行されるイベントリスナー内でも、この実装ならパフォーマンスへの影響は皆無といっていい。

—

プロダクションレベルの「Promise」判定関数

では、真に信頼できる判定ロジックを実装しよう。我々が知りたいのは「このオブジェクトは `Promise.resolve()` に渡しても安全か」という一点に尽きる。

/

  • 堅牢なPromise判定の実装
  • @param {any} value – 判定対象のデータ
  • @returns {boolean} – 本当にPromiseとして扱えるか

/
function isPromise(value) {
// 1. 基本的なNullチェック
if (value == null) return false;

// 2. 厳密な型チェック
// 多くの環境で、Promiseはオブジェクトまたは関数として振る舞う
if (typeof value !== ‘object’ && typeof value !== ‘function’) return false;

// 3. thenableであるかを確認(これが最も重要)
// Promise/A+準拠の全てのPromiseは、thenメソッドを持つ
return typeof value.then === ‘function’;
}

なぜ「Symbol.toStringTag」に頼ってはいけないのか?

一部のエンジニアは `Object.prototype.toString.call(value) === ‘[object Promise]’` を使う。これは確かに便利だが、改竄が可能だ。`Symbol.toStringTag` を上書きすれば、ただのプレーンなオブジェクトを `Promise` と偽ることができてしまう。

セキュリティを重視するライブラリ開発や、外部からの入力を扱うゲートウェイ部分では、こうした「メタデータ」に依存する判定は避けるべきだ。あくまで「振る舞い(Behavior)」、すなわち `then` メソッドの存在こそが、唯一の真実である。

—

メモリとパフォーマンス:非同期の競合を回避する設計

Promiseの判定を間違えると、最悪の場合「無限ループ」や「メモリリーク」を引き起こす。例えば、`then` メソッド内で自身を解決するような循環参照を持つThenableを、安易に `await` し続ければどうなるか?

  • 非同期の競合: 判定処理自体が重いと、マイクロタスクキューが溢れ、ブラウザのメインスレッドをブロックする。
  • メモリ効率: 過剰な判定ロジックでクロージャを多用すれば、GC(ガベージコレクション)の頻度が高まり、UIのジャンクを引き起こす。

結論として、判定は「最小コストで、振る舞いのみを問う」。

君たちが作るWebアプリケーションが、複雑な状態遷移を抱えるものならば、型判定は「守り」のための静的なルールではなく、システムを動かし続けるための「動的なインターフェース」であるべきだ。

次に `if (promise instanceof Promise)` を書くとき、一瞬だけ立ち止まってほしい。そのPromiseは、君のアプリケーションという戦場を生き残れるほど、信頼に値するものだろうか?

さあ、コードに戻ろう。最適化の余地はまだ、そこら中に転がっているはずだ。

コメント

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