- $.whenはPromise.allの完全互換ではありません
- まずPromise.allの動作を整理する
- $.whenの基本動作
- 最大の違い:返り値の構造
- 失敗時の挙動の違い
- 非同期以外も受け取る挙動
- 単一引数時の挙動
- なぜこの違いが生まれたのか
- 移行時の注意点
- まとめ
$.whenはPromise.allの完全互換ではありません
結論から書きます。
jQueryの`$.when()`はPromise.allと似ていますが、同じ挙動ではありません。
特に次の2点が大きく違います。
- 受け取る値の形
- エラー時の挙動
この違いを知らずに置き換えると、非同期処理の結果が壊れます。
実際、jQueryからES6へ移行する際のトラブルの多くがここで発生します。
まずPromise.allの動作を整理する
Promise.allは複数のPromiseがすべて成功したときに結果をまとめて返します。
Promise.all([ fetch("/a.json").then(r=>r.json()), fetch("/b.json").then(r=>r.json()) ]).then(([a,b])=>{ console.log(a,b); });
特徴は次の通りです。
- 全成功でthenに入る
- 1つでも失敗するとcatch
- 結果は配列で返る
つまり「すべて成功しないと進まない」構造です。
$.whenの基本動作
同じことをjQueryで書くとこうなります。
$.when( $.ajax("/a.json"), $.ajax("/b.json") ).done(function(a,b){ console.log(a,b); });
ここまでは同じに見えます。
しかしここからが違います。
最大の違い:返り値の構造
Promise.allは配列を返します。
[aData, bData]
一方、`$.when()`は配列ではありません。
各引数が「別の引数」として渡されます。
さらに重要な点があります。
jQuery.ajaxの戻り値は単なるデータではありません。
実際に`a`の中身はこうです。
| 内容 |
| data |
| statusText |
| jqXHR |
つまり`$.when()`の引数`a`は「レスポンスそのもの」ではなく、
レスポンス情報のセットです。
したがって、次のコードは誤りです。
$.when($.ajax("/a.json")).done(function(a){ console.log(a.name); });
正しくはこうなります。
$.when($.ajax("/a.json")).done(function(a){ console.log(a[0].name); });
この違いが最初の落とし穴です。
失敗時の挙動の違い
Promise.allは1つでも失敗するとcatchに入ります。
Promise.all([p1,p2]).catch(()=>console.log("失敗"));
一方`$.when()`はfailに入りますが、
失敗した通信の情報だけが渡されます。
$.when(p1,p2).fail(function(err){ console.log(err); });
ここで問題が起きます。
- どのリクエストが失敗したか分かりにくい
- 成功した結果が失われる
Promise.allも同様に全体が失敗扱いですが、
Promiseは個別catchやallSettledで回避できます。
jQueryにはこれがありません。
非同期以外も受け取る挙動
`$.when()`にはPromise.allにない特徴があります。
Promiseではない値も受け取れます。
$.when(10, "abc").done(function(a,b){ console.log(a,b); });
これは即時実行されます。
Promise.allでは必ずPromiseに包まれます。
この仕様により、コードが予想外に同期実行されることがあります。
jQuery特有のバグの原因です。
単一引数時の挙動
さらに重要な違いです。
Promise.allは常にPromiseを返します。
しかし`$.when()`は引数が1つのとき、
そのDeferredをそのまま返します。
var a = $.when($.ajax("/a"));
この結果、チェーンのタイミングが変わることがあります。
Promiseへの移行時に挙動がズレる典型例です。
なぜこの違いが生まれたのか
理由は設計思想です。
jQuery DeferredはPromise仕様が確立する前に作られました。
そのため「利便性」を優先しています。
- 複数通信を簡単にまとめる
- 同期値も扱える
- 柔軟にコールバックを呼ぶ
一方Promiseは「安全性」と「一貫性」を優先しています。
- 常に非同期
- 常に同じ戻り値
- 状態は変更不可
つまり`$.when()`は便利ツール、
Promise.allは仕様です。
移行時の注意点
jQueryからPromiseへ置き換える際、最も危険なのはこの書き方です。
$.when($.ajax("/a"), $.ajax("/b")) .done(function(a,b){ ... });
これを単純にPromise.allへ置き換えると、
受け取るデータ構造が変わり動かなくなります。
正しい置換は次です。
Promise.all([ fetch("/a").then(r=>r.json()), fetch("/b").then(r=>r.json()) ]).then(([a,b])=>{ ... });
ここを修正しないと、本番でのみエラーが出るケースがあります。
まとめ
`$.when()`とPromise.allは目的は同じです。
「複数の非同期完了を待つ」ことです。
しかし設計はまったく異なります。
- $.when … コールバック中心・柔軟
- Promise.all … 仕様中心・厳密
見た目が似ているため混同されやすいですが、
移行時に最も事故が起きやすいポイントです。
jQueryコードを現代のJavaScriptへ移すときは、
まずこの違いを確認することが安全な第一歩になります。