質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.20%

jQuery の Deferred や when / then による同期的な処理の後の `return` を返す方法

受付中

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 3,435

yasshcy

score 6

 前提・実現したいこと

jQuery で以下のような
API【A】へリクエスト、そのレスポンス値を使って
API【B】へリクエスト、そのレスポンス値を使って
オブジェクトに格納し、 return でデータを渡せるようなものを書いています。

jQuery の Deferred や when / then による同期的な記述をしていますが
以下の記述だと、最終的な return が同期的に返せておらず、
どの記述が良いか、ご教授、アドバイスをいただけると嬉しいです。

 発生している問題・エラーメッセージ

該当のソースコードの ※ の部分が同期的に return が取れず困っている箇所です。

 該当のソースコード

$(function() {

  'use strict';

  function parent () {

    // 最終的に `parent` を外のjsから実行した時の return に利用したい
    var result = [];

    // childBの処理で利用したい配列。中身はchildAで作成する
    var useB = [];

    // APIのURL
    var jsonA = 'https://www.hoge.jp/apiA.php';
    var jsonB = 'https://www.hoge.jp/apiB.php';

    function childA () {

      // 同期を取るため延期させるDeferredオブジェクト作成
      var d = new $.Deferred();

      // Ajax
      $.ajax({
        url: jsonpA,
        dataType: 'jsonp',
        jsonpCallback: 'jsonpA',
        cache : true,
      })
      .then( function( res ) {

        // 取ってきたマニュアル jsonp を eachで回して
        $( res ).each( function (index, el) {

            // childBで利用するためのデータを格納
            useB.push({ el.hoge });

        });

        useB = useB.join();

        // Deferred 延期の解決
        d.resolve();

      });

      // プロミス作ってDeferredを返す
      return d.promise();

    }

    function childB () {

      // 同期を取るため延期させるDeferredオブジェクト作成
      var d = new $.Deferred();
      // Ajax
      $.ajax({
        url           : jsonB,
        dataType      : 'jsonp',
        jsonpCallback : 'jsonB',
        cache         : true,
      })
      .then( function ( res ) {

        // ここで 最終的に return で利用したいデータを蓄積する
        $( useB ).each( function (index, el) {

          // 足していく
          result.push({
            'test01' : el.element01,
            'test02' : el.element02
          });

        });

        // Deferred 延期の解決
        d.resolve();

      });

      // プロミス作ってDeferredを返す
      return d.promise();

    }

    // childAを実行してchildBを実行
    $.when(

      childA()

    )
    .then(childB)

    // ※ ここに書いても結局 when then を待たず時実行される。。同期的ではない
    return result;

  }

  // windowオブジェクトに追加して外部jsから呼び出せるようにする
  window.parent = parent();

});

これを読み込んだ後に別のjs(またはhtml上で)で parent として呼び出して利用しています。

 試したこと

上記だと、 $when / then から外れた所で return を書いているので同期的にならないと考え以下のように記述したのですが、
その場合、 function parent の return としては扱われないので、困りました。

$(function() {

  'use strict';

  function parent () {

    // 最終的に `parent` を外のjsから実行した時の return に利用したい
    var result = [];

    // childBの処理で利用したい配列。中身はchildAで作成する
    var useB = [];

    // APIのURL
    var jsonA = 'https://www.hoge.jp/apiA.php';
    var jsonB = 'https://www.hoge.jp/apiB.php';

    function childA () {

      // 同期を取るため延期させるDeferredオブジェクト作成
      var d = new $.Deferred();

      // Ajax
      $.ajax({
        url: jsonpA,
        dataType: 'jsonp',
        jsonpCallback: 'jsonpA',
        cache : true,
      })
      .then( function( res ) {

        // 取ってきたマニュアル jsonp を eachで回して
        $( res ).each( function (index, el) {

            // childBで利用するためのデータを格納
            useB.push({ el.hoge });

        });

        useB = useB.join();

        // Deferred 延期の解決
        d.resolve();

      });

      // プロミス作ってDeferredを返す
      return d.promise();

    }

    function childB () {

      // 同期を取るため延期させるDeferredオブジェクト作成
      var d = new $.Deferred();
      // Ajax
      $.ajax({
        url           : jsonB,
        dataType      : 'jsonp',
        jsonpCallback : 'jsonB',
        cache         : true,
      })
      .then( function ( res ) {

        // ここで 最終的に return で利用したいデータを蓄積する
        $( useB ).each( function (index, el) {

          // 足していく
          result.push({
            'test01' : el.element01,
            'test02' : el.element02
          });

        });

        // Deferred 延期の解決
        d.resolve();

      });

      // プロミス作ってDeferredを返す
      return d.promise();

    }

    // childAを実行してchildBを実行
    $.when(

      childA()

    )
    .then(childB)
    .then(function(){
      // ※ ここで return させても `parent` の return としては扱われない。。
      return result;
    })

  }

  // windowオブジェクトに追加して外部jsから呼び出せるようにする
  window.parent = parent();

});

どうぞよろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+1

同期を取りたいparentでpromiseの終了を待つ必要があると思います。

childBのpromiseをreturnして、parent側で、戻されたpromiseに対して.thenする感じで、処理できませんか?

 追記

若干怪しいところがあるかもしれませんが・・・
概ねこんなイメージでしょうか?

(説明に不要と思った部分は、オリジナルソースから省略しました)

  function parent () {
    // parentのDeferredオブジェクト作成
    var parent_dfd = new $.Deferred();

    // 最終的に `parent` を外のjsから実行した時の return に利用したい
    var result = [];

    function childA () {

      var d = new $.Deferred();

      // Ajax
      $.ajax({
        url: jsonpA,
      })
      .then( function( res ) {
        ...

        d.resolve();
      });

      return d.promise();

    }

    function childB () {

      // Ajax
      $.ajax({
        url: jsonpB,
      })
      .then( function ( res ) {

        $( useB ).each( function (index, el) {

          result.push({
            'test01' : el.element01,
            'test02' : el.element02
          });

        });

        // parentのDeferredを解決
        parent_dfd.resolve(result);

      });
    }

    childA()
    .then(childB);

    return parent_dfd.promise();
  }

  // windowオブジェクトに追加して外部jsから呼び出せるようにする
  window.parent = parent();

});

以下、呼び出し側

  parent().then(function (result) {
    //resultの処理
  });

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/21 17:17 編集

    ありがとうございます。

    > 同期を取りたいparentでpromiseの終了を待つ必要があると思います。
    確かに、今parentの処理では Deferred の作成と prromise は持っていないです。

    > childBのpromiseをreturnして、
    今、childB で `return d.promise();` となっている所で `promise` せず `return` をして

    > parent側で、戻されたpromiseに対して.thenする感じ、処理できませんか?
    こういう感じでしょうか。

    ```
    // childAを実行してchildBを実行
    $.when(

    childA()

    )
    .then(
    childB,
    function () {
    return d.promise();
    }
    )
    ```

    うまくは行きませんでしたが、
    > parent側で、戻されたpromiseに対して.thenする感じ
    がイマイチピンときませんでした。。すみません。

    キャンセル

  • 2018/08/21 17:53

    すいません、parentの定義をあやふやにしてました。

    私がparentと示したのは、parentの呼び出し側のことでしたが、
    よくよく考えたら、私の知識では、childBのpromiseをparentのreturnとして返す方法が、見つけられませんでした(>_<)

    おっしゃる通り、parentにdeferredを作成して、promiseをreturnし、childBの中で、そのdeferredをresolveするイメージになると思います。その場合、childBのdeferredは不要だと思います。

    のちほど、回答に追記します。

    キャンセル

0

コールバックでよかったら引数つけて呼べばいいのでは?未検証ですが

function parent (cb) {
    .then(childB)
    .then(function(){
      // ※ ここで return させても `parent` の return としては扱われない。。
      //return result;
      cb(result);
    })

呼び出し

    window.parent(function(result){
        // result使って処理
    })

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/21 18:56 編集

    ありがとうございます。

    呼び出しの引数を `result` => `cb` に変更した所、

    # 呼び出し

    ```
    window.parent(function(cb){
    // result使って処理
    })
    ```

    ちゃんと処理を待ってくれました。
    callbackでの順番待ちはうまく行きました。ありがとうございます。

    ただ、これはもともとの私の構造が悪いのかもしれませんが、
    今回全体を

    ```
    $(function() {
    function parent () {
    〜 略 〜
    }
    });
    ```

    で囲ったものを、外から実行できるようにする必要があったので、

    外から実行した際に `undefined` になりオブジェクトが受け取れませんでした。

    もしかしたら、即時関数で囲うこと自体がナンセンスかもしれませんね。。

    キャンセル

0

jsonpなのですからコールバックのjsonpAからjsonpBを呼ぶだけでは?

 sample

<script>
$(function(){
  $('#btn').on('click',function(){
    $.ajax({
    url: 'ajax.php',
    data:{"id":1},
    dataType: 'jsonp',
    jsonpCallback: 'jsonpA',
    cache : true,
    });
  });
});
function jsonpA(data){
  data.id=data.id+"23";
  $.ajax({
    url: 'ajax.php',
    data:data,
    dataType: 'jsonp',
    jsonpCallback: 'jsonpB',
    cache : true,
    });
}
function jsonpB(data){
  console.log(data);
}
</script>
<input type="button" id="btn" value="test">
  • ajax.php
<?PHP
$callback=filter_input(INPUT_GET,"callback");
if(is_null($callback)) $callback="void";
$id=filter_input(INPUT_GET,"id");
$data=["id"=>$id];
header("Content-Type:application/javascript");
print "{$callback}(".json_encode($data).");";
?>

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/21 19:03

    ありがとうございます。

    このcallbackを自由に決めてよければ、この方法がsimpleかもしれませんね。
    ただ、今回webAPIの仕様上、jsonp取得のcallbackは指定されていているので
    今回のケースでは適用できなさそうです。

    キャンセル

  • 2018/08/22 13:11

    > callbackを自由に決めてよければ

    ごめんなさい、意味がわかりません
    ご自身が例示したsample自体にコールバック関数名を指定していますよね?
    jsonpなのに指定したコールバック名でデータを返してくれないのでしょうか?

    キャンセル

  • 2018/09/03 12:35

    説明が足らず申し訳ありません。
    そしてレスが遅くて申しわけありません。。

    > ご自身が例示したsample自体にコールバック関数名を指定していますよね?
    はい、sampleに `jsonpCallback: 'jsonpA',` とコールバック関数を指定しています。

    > jsonpなのに指定したコールバック名でデータを返してくれないのでしょうか?
    webAPIは別途、他の人が用意したものなのですが、これに問い合わせると以下のような形でレスポンスが返ってきます。

    ```
    jsonpA({
    "list": [
    {
    "property1": "value1",
    "property2": "value2"
    }
    ]
    })
    ```

    上記の `jsonpA` は js側で指定している
    `jsonpCallback: 'jsonpA',` の値と一致していなければならないと、思っています。
    これはwebAPI(php)側で指定されているので、変更できない、という意味で申し上げました。

    キャンセル

  • 2018/09/03 12:45

    だから、jsonpでjsonpAの引数でjsonデータが返るわけですから
    私が最初に提示したとおりの処理でよいのでは?
    jsonpAはユーザー関数を用意してくださいという意味ですから

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.20%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る