jQuery.when()

参考: jQuery.when() - jQuery API

jQuery.when( deferreds ) 返り値:Promise

概要

一つ以上のオブジェクト(主に Deferred オブジェクト)を受け取り Promise オブジェクトを生成する。返された Promise オブジェクトに 登録したコールバックは、受け取ったオブジェクトの状態に基づいて実行される。

用法

  • jQuery.when( deferreds )

    • 1.5で追加
    deferreds

    一つ以上の Deferred オブジェクト。もしくは プレーンな JavaScript オブジェクト。

詳細

※この記事はバージョン 1.8 の時点での記事です。1.9 では、 コールバックのコンテキスト(this)が Deferred オブジェクトから その Promise オブジェクトに変更されています。ご注意下さい。 (参考)

jQuery.when()にオブジェクトを渡すと、 Promise オブジェクトが返ります。 Promise オブジェクトですので、.done().fail().progress()(1.7以降)等で コールバックを登録できます。 登録したコールバックは、 渡したオブジェクトの種類や状態に基づいて実行されます。

引数が Deferred オブジェクト一つだとそのオブジェクトの Promise オブジェクトが返ります。それ以外の引数を与えると 内部で新規作成した Deferred オブジェクトの Promise オブジェクトが 返ります。

Promise オブジェクトは渡した Deferred オブジェクトが全て resolved になった時に .done()等で登録したコールバック (以下 done コールバック)が実行されます。Deferred ではない オブジェクトが渡された場合、そのオブジェクトは resolved の Deferred として扱われます。null, false, undefined も resolved になります。

一方、渡された Deferred オブジェクトのどれか一つでも rejected になった時は、 Promise オブジェクトは rejected になります。 この時、rejected になった時に実行されるコールバック (以下 fail コールバック)が実行された時点では、rejected になった オブジェクト以外の Deferred オブジェクトはまだ resolved に なっていない(pending 状態)かもしれません。

// url1, url2 にはそれぞれ URL が入るとする
$.when( $.ajax( url1 ), $.ajax( url2 ) ).done(
  function(result1, result2){
    // 両方成功すると処理される
}).fail(
  function(jqXHR, textStatus, errorThrown){
    // 片方でも失敗すると処理される
  }
);

$.when( { someProp: "abc" } ).done(function(obj){
  // resolved として扱われるためこちらが処理される
  console.log(obj.someProp); // → "abc"
})
.fail(function(){
  // こちらは決して実行されない
});

コールバックへの引数

Promise オブジェクトに登録したコールバックに渡される引数は、 jQuery.when()に渡す引数の数や、 渡した Deferred オブジェクトが実行する .resolve().reject()等に渡す 引数の数で変化します。

jQuery.when()に渡すオブジェクトの数が 一つだけの場合、渡すオブジェクトが Deferred オブジェクトであるなら、 コールバックに渡される引数は .resolve().reject().notify()(1.7 から)等に渡す 引数と同じになります。渡すオブジェクトが Deferred でないなら そのオブジェクトがそのまま done コールバックに渡されます。

// url1 は URL が入るとする
$.when( $.ajax( url1 ) ).done(
  function(data, textStatus, jqXHR){
    // success コールバックと同じ引数
}).fail(
  function(jqXHR, textStatus, errorThrown){
    // error コールバックと同じ引数
  }
);

$.when( { someProp: "abc" } ).done(function(obj){
  // when() に渡されるオブジェクトがそのまま渡される
  console.log(obj.someProp); // → "abc"
});

jQuery.when()に渡すオブジェクトの数が 複数ある場合、done コールバックに渡される引数は、 .resolve()等に引数が複数渡された時は配列で ひとまとめにして、jQuery.when()への引数と 同じ順に渡されます。例えば、 jQuery.when(defer1, obj1, defer2)と 渡したとすると、done コールバックの第一引数には defer1.resolve()に渡した引数が入り、 第二引数にはobj1がそのまま入り、 第三引数にはdefer2.resolve()に渡した引数が 入ります。

// url1 には URL が入るとする
var defer2 = $.Deferred();
setTimeout( function(){ defer2.resolve("defer2"); }, 1000);
$.when( $.ajax( url1 ), { someProp: "abc" }, defer2 )
  .done( function(result1, obj, result2){
    // result1 は [data, textStatus, jqXHR]
    // obj は { someProp: "abc" }
    // defer2 は "defer2" (単数なら配列でまとめない)
});

一方、複数引数のjQuery.when()での fail コールバックに渡される引数は、 渡された Deferred オブジェクトのうち 最初に実行された.reject()等に渡された引数が そのまま渡されます。

var defer1 = jQuery.Deferred(),
    defer2 = jQuery.Deferred(),
    defer3 = jQuery.Deferred();
setTimeout(function(){
  defer1.resolve("arg1", "arg2", "arg3");
  defer2.reject("arg4", "arg5");
  defer3.reject("arg6");
}, 1000);
$.when(defer1, defer2, defer3).fail(function(a1, a2, a3){
  // 最初に実行される defer2.reject() への引数が渡される
  console.log(typeof a1); // string
  console.log(typeof a2); // string
  console.log(typeof a3); // undefined

  console.log(a1); // "arg4"
  console.log(a2); // "arg5"
  console.log(a3); // undefined
});

複数個の引数をjQuery.when()に渡した場合の progress コールバックへの引数は少し複雑です。 まず、jQuery.when()に渡した Deferred オブジェクトと同じ位置に、その Deferred オブジェクトの.notify()(.notifyWith()) に渡した引数が、progress コールバックに渡されます。 例えば$.when(defer1, obj1, defer2) とした場合、progress コールバックに渡される引数は、 第1引数にdefer1.notify()への引数が、 第2引数にはundefinedが、 第3引数にdefer2.notify()への引数が渡されます。 notify()への引数が複数個ある場合は 配列でまとめて渡されます。

そして、progress コールバックには、 それぞれの Deferred オブジェクトが最後に実行した .notify()への引数が渡されます。例えば、 上の例でdefer1.notify(60)と実行され progress コールバックが呼ばれた時、まだ defer2.notify()が実行されていなかった場合、 progress コールバックには第1引数に 60 が入っているだけで、 他の引数はundefinedのままになります。

// 1.7 以降
var defer1 = jQuery.Deferred(),
    defer2 = jQuery.Deferred(),
    defer3 = jQuery.Deferred();

setTimeout(function(){
  defer1.notify("arg1", "arg2", "arg3");
}, 1000);
setTimeout(function(){
  defer2.notify("arg4", "arg5");
}, 2000);
setTimeout(function(){
  defer1.notify("arg6");
}, 3000);

jQuery.when(defer1, { someProp: "someValue" }, defer2).progress(
  function(a1, a2, a3){
  /*
  初回:
  a1: ["arg1", "arg2", "arg3"],  a2: undefined,  a3: undefined
  2回目:
  a1: ["arg1", "arg2", "arg3"],  a2: undefined,  a3: ["arg4", "arg5"]
  3回目:
  a1: "arg6",  a2: undefined,  a3: ["arg4", "arg5"]
   */
});

以下は表にまとめたものです。

jQuery.when()への引数に対する各種コールバックの引数
単数(Deferred)単数(その他)複数
done resolve への引数 引数そのまま Deferred なら resolve への引数(複数なら配列化)。
その他ならそのまま
fail reject への引数 実行されない 最初の reject への引数
progress notify への引数 実行されない Deferred なら notify への引数(複数なら配列化)
初期は undefined。後続の notify で上書きされる

コールバックのコンテキスト(this)

Promise オブジェクトに登録したコールバックのコンテキスト (コールバック中でthisで参照される値)は バージョン 1.7 までと 1.8 で異なる場合があります。

fail コールバックに関しては 1.8 まで同じで、 jQuery.when()に渡した Deferred オブジェクトの中で 最初に.reject()したオブジェクトが thisになります。この時.rejectWith()thisの変更が可能です。

var defer1 = $.Deferred();
var defer2 = $.Deferred();
var defer3 = $.Deferred();

$.when( defer1, defer2, defer3 ).fail(function(a1, a2, a3){
  console.log( this ); // Object { someProp="someValue" }
  console.log(a1);     // "arg2"
  console.log(a2);     // undefined
  console.log(a3);     // undefined
});

defer1.resolve("arg1");
defer2.rejectWith({ someProp: "someValue" }, ["arg2"]);
defer3.reject("arg3");

done コールバックに関しては、jQuery.when()への 引数が Deferred オブジェクト一つの場合は バージョンの違いは無く、その Defereed オブジェクトが thisになります。.resolveWith()での thisの変更も可能です。

Deferred オブジェクト以外が一つjQuery.when()に 渡された場合、バージョン 1.7 までは、 jQuery.when()の内部で新規作成された Deferred オブジェクトが done コールバックの thisとなります。 この場合は.resolveWith()thisの変更はできません。 バージョン 1.8 では グローバルオブジェクトが done コールバックのthisになります。

jQuery.when({ someProp: "someValue" }).done(function(a1){
  console.log( this );
  // → 1.7 まで
  //   Object { done=function(), fail=function(), ...}
  // → 1.8
  //   Window(グローバルオブジェクト)
});

複数の引数をjQuery.when()に 渡した場合、バージョン 1.7 までは単数の時と同じく jQuery.when()の内部で新規作成された Deferred オブジェクトが done コールバックの thisとなります。これも.resolveWith()thisの変更はできません。
バージョン 1.8 では、それぞれの引数に対応する値をまとめた 配列が done コールバックのthisになります。 引数が Deferred オブジェクトの場合はそのまま Deferred オブジェクト(または.resolveWith()で 指定したオブジェクト)が、 引数が Deferred オブジェクト以外の場合は undefinedがその対応する値になります。 配列の値の順番はjQuery.when()への引数と同じです。

var defer1 = $.Deferred();
var defer2 = $.Deferred();
defer1._defType = "defer1";
defer2._defType = "defer2";

jQuery.when(defer1, { someProp:"someValue" }, defer2).done(function(){
  console.log( this );
  // → 1.7 まで
  //   Object { done=function(), fail=function(), ...}
  // → 1.8
  //   [Object { _defType="defer1",  resolve=function(), ...},
  //    undefined,
  //    String { 0="s",  1="o",  2="m",  ...} ]
});

defer1.resolve();
defer2.resolveWith("someString");

progress コールバックに関しては、jQuery.when()への 引数が一つの場合は引数の Deferred オブジェクトが thisになります。.notifyWith()での thisの変更も可能です。 バージョンによる違いはありません。

jQuery.when()に引数を複数渡した場合、 バージョン 1.7 ではjQuery.when()が返した Promise オブジェクトがthisになります。 .notifyWith()thisの変更はできません。
バージョン 1.8 では、done コールバックと似て、 jQuery.when()に渡した Deferred オブジェクト( もしくは.notifyWith()で指定した値)を まとめた配列がthisになりますが、その値は 対応する Deferred オブジェクトが.notify()( .notifyWith())を 一回でも実行しない限り配列に格納されず、未実行の間は undefinedになります。 .notify()が実行されればその Deferred オブジェクトが 配列の値になります。 この配列は progress コールバック間で共通であるため、 .notifyWith()で値を上書きすると以降の progress コールバックで変更が反映されます。

var defer1 = $.Deferred();
var defer2 = $.Deferred();
defer1._defType = "defer1";
defer2._defType = "defer2";

var prom = jQuery.when(defer1, { someProp:"someValue" }, defer2);
prom._defType = "promise";

prom.progress(function(){
  console.log( this );
  /*
  → 1.7 (3回とも)
    Object { _defType="promise", done=function(), ...}
  → 1.8
    初回:
    [_defType="defer1"のDeferred, undefined, undefined]
    2回目:
    [_defType="defer1"のDeferred, undefined, { _from:"notifyWith" }]
    3回目:
    [_defType="defer1"のDeferred, undefined, _defType="defer2"のDeferred]
  */
});

setTimeout(function(){
  defer1.notify();
}, 1000);
setTimeout(function(){
  defer2.notifyWith({ _from: "notifyWith"})
}, 2000);
setTimeout(function(){
  defer2.notify()
}, 3000);

以下は表にまとめたものです。

jQuery.when()への引数に対する各種コールバックのコンテキスト
単数(Deferred)単数(その他)複数
done そのDeferred 1.7 まで:新規作成の Deferred
1.8:グローバルオブジェクト
1.7 まで:新規作成の Deferred
1.8:配列。値は引数の Deferred or resolveWith で指定した値 or undefined
fail そのDeferred 実行されない 最初に reject した Deferred
progress そのDeferred 実行されない 1.7:返り値の Promise
1.8:配列。値は引数の Deferred or notifyWith で指定した値 or undefined。 初期は undefined。 後続の notify で設定される

スポンサードリンク