かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

jQuery.whenのdone()にAjaxで取得したデータを加工して渡したい

$.when()複数の非同期処理を行って、.done()で結果を受け取ってその後の処理をするのですが、特に非同期処理がAjaxの時、$.when().done()Ajaxで取得できた値を受け取って加工するのではなく、Ajax側で取得したデータを加工して$.when()done()に渡したい時のやりかたのメモ。

var foo = $.ajax({
  url : "foo_API_URL", // {"text": "foo"} が返ってくるとする
  type : "GET",
  dataType : "json",
  data: data
}).done(function(res) {
  var resText = res.text; // "foo"
  // ↓ 加工済みのデータを返したい
  return resText
});

var bar = function() {
  return 'bar';
};

$.when(foo, bar()).done(function(res1, res2){
  // fooはAjaxが完了した段階で値が返されるので
  // res1は resText ではなく、resになる
  console.log(res1); // -> Object {text: "foo"}
  console.log(res2); // -> "bar"
});

jQuery.Deferredを使う

fooはajaxなのでjqXHRが返され、Ajaxが完了した時に内部的にDeferred.resolve()が実行されている様です。
なので、$.ajax.done内でreturnをしてもその前にDeferred.resolve()で取得した結果が返されてしまっているのだと思います。
であれば、明示的にDeferredオブジェクトを返す関数を呼び出すようにして、Deferred.resolve()で加工済みの値を返すようにすれば、実現できそうです。

var foo = function() {
  var defer = $.Deferred();
  $.ajax({
    url : "foo_API_URL", // {"text": "foo"} が返ってくるとする
    type : "GET",
    dataType : "json",
    data: data
  }).done(function(res) {
    var resText = res.text; // foo
    // defer.resolve() を使って値を返す
    defer.resolve(resText);
  });
  // Deferred オブジェクトを返す
  return defer.promise(this);
};

var bar = function() {
  return 'bar';
};

// fooは関数になっているので、呼び出し方が変わる
$.when(foo(), bar()).done(function(res1, res2){
  // fooはDeferredオブジェクトが返されるので
  // res1は defer.resolve( resText )の resText が渡される
  console.log(res1); // -> "foo"
  console.log(res2); // -> "bar"
});

感想

Ajaxのデータを加工して渡したい場合は関数にしなければならないので、$.when()での呼び出し方が変わってしまいます。
$.when()内での呼び出し方が () が有るものと、無いものができて少しキモチワルイかもしれません。
僕は引数に直接ajaxを入れて、()無しで$.when()の中に入れると、どのタイミングで引数のAjaxが実行されてるのか見通しが悪い用に感じ、また()有りと無しの引数が共存してるのに違和感があったので、jQuery.Deferredを使うパターンでajaxそのままで済む時も一旦ajaxをラップした関数にしてreturn defer.promise(this);を使い$.when()内で呼び出すの処理は()有りで統一しています。一度関数を呼び出す分少し無駄な処理をしていると思いますが。


[参考]

JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス

JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス