Deferredの配列を$ .when()に渡します


447

これが起こっていることの不自然な例です:http : //jsfiddle.net/adamjford/YNGcm/20/

HTML:

<a href="#">Click me!</a>
<div></div>

JavaScript:

function getSomeDeferredStuff() {
    var deferreds = [];

    var i = 1;
    for (i = 1; i <= 10; i++) {
        var count = i;

        deferreds.push(
        $.post('/echo/html/', {
            html: "<p>Task #" + count + " complete.",
            delay: count
        }).success(function(data) {
            $("div").append(data);
        }));
    }

    return deferreds;
}

$(function() {
    $("a").click(function() {
        var deferreds = getSomeDeferredStuff();

        $.when(deferreds).done(function() {
            $("div").append("<p>All done!</p>");
        });
    });
});

全部やりたい!すべての遅延タスクが完了した後に表示されますが、遅延$.when()オブジェクトの配列を処理する方法を認識していないようです。"全部終わった!" 配列はDeferredオブジェクトではないため、最初に発生します。jQueryは先に進み、それが完了したと見なします。

のようにオブジェクトを関数に渡すことができることは知っていますが、$.when(deferred1, deferred2, ..., deferredX)解決しようとしている実際の問題の実行時にDeferredオブジェクトがいくつあるかは不明です。



以下のこの非常に古い質問に対する新しい、より単純な回答を追加しました。同じ結果を得るために、配列を使用する必要はまったくありませ$.when.apply
コーディングを終了

質問の件名が具体的すぎたため、ロールバックしました(これはAJAXの問題だけではありません)
Alnitak

回答:


732

値の配列を渡すために、任意の通常それらは別々のパラメータを、使用することを期待し機能Function.prototype.applyなので、この場合には次のものが必要です。

$.when.apply($, my_array).then( ___ );

http://jsfiddle.net/YNGcm/21/を参照してください

ES6では、代わりに... スプレッド演算子を使用できます。

$.when(...my_array).then( ___ );

どちらの場合も、.thenハンドラーが必要とする仮パラメーターの数が事前にわかっていることはまずないため、そのハンドラーはarguments、各promiseの結果を取得するために配列を処理する必要があります。


4
これは素晴らしいです。:)私はグーグル経由でそのような単純な変更をしぼることができなかったことに驚いています!
adamjford 2011

9
それは一般的な方法ではなく、固有のだから、それはだ$.when- f.apply(ctx, my_array)呼び出しますfthis == ctxとに設定した引数の内容my_array
アルニタク2011

4
@Alnitak:JavaScriptを今まで何年も書いていることを考えると、その方法について知らなかったことに少し恥ずかしいです。
adamjford 2011

5
FWIW、最初のパラメーターとして$vs を渡すことについての議論を伴う、より早い質問に対するEliの回答のリンクは、一読のnull価値があります。ただし、この特定のケースでは問題ではありません。
Alnitak 2011

4
@Alnitak:はい。ただし、$入力は少なくnull$.when実装が変更されても安全です(この場合はそうではないかもしれthisませんが、デフォルトで変更しないでおく必要があるのはなぜですか)。
TomaszZieliński12年

109

上記の回避策(ありがとう!)はresolve()、jQueryがdone()fail()コールバックを配列ではなく個々のパラメーターで呼び出すため、遅延オブジェクトのメソッドに提供されたオブジェクトを取得する問題に適切に対応していません。これは、arguments疑似配列を使用して、遅延オブジェクトの配列によって返されたすべての解決済み/拒否されたオブジェクトを取得する必要があることを意味します。

$.when.apply($,deferreds).then(function() {
     var objects=arguments; // The array of resolved objects as a pseudo-array
     ...
};

遅延オブジェクトの配列を渡したので、結果の配列を取得すると便利です。また、擬似配列の代わりに実際の配列を取得して、次のようなメソッドを使用できるようにするとよいでしょう。Array.sort()

これらの問題に対処するwhen.jswhen.all()メソッドから着想を得た解決策を次に示します。

// Put somewhere in your scripting environment
if (typeof jQuery.when.all === 'undefined') {
    jQuery.when.all = function (deferreds) {
        return $.Deferred(function (def) {
            $.when.apply(jQuery, deferreds).then(
                function () {
                    def.resolveWith(this, [Array.prototype.slice.call(arguments)]);
                },
                function () {
                    def.rejectWith(this, [Array.prototype.slice.call(arguments)]);
                });
        });
    }
}

次のように、単にdeferreds / promisesの配列を渡し、解決済み/拒否されたオブジェクトの配列をコールバックで返すことができます。

$.when.all(deferreds).then(function(objects) {
    console.log("Resolved objects:", objects);
});

6
resolveWithとrejectWithを使用して、「this」と同じ元の遅延を取得することもできます。deferred.resolveWith(this、[Array.prototype.slice.call(arguments)])など
Jamie Pate

1
コードに小さな問題があります。配列に要素が1つしかない場合、結果の配列は、単一の要素を持つ配列ではなく、その結果のみを返します(配列を必要とするコードが壊れます)。修正するには、のvar toArray = function (args) { return deferreds.length > 1 ? $.makeArray(args) : [args]; }代わりにこの関数を使用しますArray.prototype.slice.call
Luan Nico

うーん、これは404をキャッチしていないようです。
t.mikael.d 2016

理由が判明したため、代わりに.failを.rejectにする必要があります-404をキャッチできます。
t.mikael.d 2016

38

whenメソッドを配列に適用できます:

var arr = [ /* Deferred objects */ ];

$.when.apply($, arr);

jQuery Deferredの配列をどのように操作しますか?


私は実際にその質問を見ましたが、その質問のすべての詳細が私の問題(そこにあった)への答えが私の頭の上を飛んだと思います。
adamjford 2011

1
@adamjford、それで気分が良くなると、私はあなたの質問をより簡単に理解できるようになりました(そして最初に、この正確な問題についての特定のGoogle検索で)。
パトリッジ

@patridge:お役に立ててうれしいです!
adamjford

これは素晴らしい答えですが、元の質問の例にこれがどのように適用されるかは不明でした。リンクされた質問を調べた後、「$。when(deferreds).done(function(){」という行を単に「$ .when.apply($、deferreds).done(function(){」に変更するだけでよいことが明らかになりました「そうですか?
ガーランド・ポープ

7

複数の並列AJAX呼び出しを呼び出す場合、それぞれの応答を処理するための2つのオプションがあります。

  1. 同期AJAX呼び出しを使用する/次々に/推奨されない
  2. すべてのがそれぞれの応答で正常に返されると、Promises'配列を使用し、$.whenを受け入れpromise、そのコールバック.doneが呼び出されますpromise

function ajaxRequest(capitalCity) {
   return $.ajax({
        url: 'https://restcountries.eu/rest/v1/capital/'+capitalCity,
        success: function(response) {
        },
        error: function(response) {
          console.log("Error")
        }
    });
}
$(function(){
   var capitalCities = ['Delhi', 'Beijing', 'Washington', 'Tokyo', 'London'];
   $('#capitals').text(capitalCities);

   function getCountryCapitals(){ //do multiple parallel ajax requests
      var promises = [];   
      for(var i=0,l=capitalCities.length; i<l; i++){
            var promise = ajaxRequest(capitalCities[i]);
            promises.push(promise);
      }
  
      $.when.apply($, promises)
        .done(fillCountryCapitals);
   }
  
   function fillCountryCapitals(){
        var countries = [];
        var responses = arguments;
        for(i in responses){
            console.dir(responses[i]);
            countries.push(responses[i][0][0].nativeName)
        }  
        $('#countries').text(countries);
   }
  
   getCountryCapitals()
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
  <h4>Capital Cities : </h4> <span id="capitals"></span>
  <h4>Respective Country's Native Names : </h4> <span id="countries"></span>
</div>


1
あなたの答えは行き過ぎであり、そしてあなたの質問のタイトルへの編集もそうしました。OPは、AJAX呼び出しを行い、遅延オブジェクトの配列を取得する方法をすでに知っています。問題の唯一のポイントは、その配列をに渡す方法でした$.when
Alnitak、2015年

5
利用可能なオプションがあれば、例を使って詳細に説明するほうが良いと思いました。そのため、反対票は必要ないと思いました。
vinayakj

2
反対投票は1.同期を提案することさえ(推奨はしていませんが)2.例の質の悪いコード(for ... inアレイを含む?!)
Alnitak

1
1.同意した、あるはずだった (not recommended)2.同意しない- for ... in配列に必要なプロパティのみが含まれている(追加のプロパティは含まれていない)ため、問題ありません。とにかく
ありがとう

1
re:2-問題は、その保証をすることができない、またはに追加するのに十分な馬鹿げている他の人々によってコピーされる可能性があることArray.prototypeです。いずれにしても、パフォーマンスが重要ではないコードの場合.mapfor/ pushループの代わりに使用するほうがよいでしょうvar promises = capitalCities.map(ajaxRequest); $.when.apply($, promises).then(fillCountryCapitals)
Alnitak、2015年

6

$.when.applyまたはを必要としない単純な代替手段として、array次のパターンを使用して、複数の並列プロミスに対して単一のプロミスを生成できます。

promise = $.when(promise, anotherPromise);

例えば

function GetSomeDeferredStuff() {
    // Start with an empty resolved promise (or undefined does the same!)
    var promise;
    var i = 1;
    for (i = 1; i <= 5; i++) {
        var count = i;

        promise = $.when(promise,
        $.ajax({
            type: "POST",
            url: '/echo/html/',
            data: {
                html: "<p>Task #" + count + " complete.",
                delay: count / 2
            },
            success: function (data) {
                $("div").append(data);
            }
        }));
    }
    return promise;
}

$(function () {
    $("a").click(function () {
        var promise = GetSomeDeferredStuff();
        promise.then(function () {
            $("div").append("<p>All done!</p>");
        });
    });
});

ノート:

  • 誰かのチェーンが順番に約束するのを見て、これを使ってこれを見つけました、 promise = promise.then(newpromise)
  • 欠点は、裏で追加のpromiseオブジェクトが作成され、最後に渡されるパラメーターは(追加のオブジェクト内にネストされるため)あまり役に立ちません。短くてシンプルですが、欲しいもののために。
  • メリットは、アレイやアレイ管理が不要なことです。

2
私が間違っている場合は修正してください。ただし、あなたのアプローチは$ .when($ .when($ .when(...)))を効果的にネストしているため、10回の反復がある場合、10レベルの深さを再帰的にネストします。独自のプロミスを返す前に、各レベルが子のネストされたプロミスを返すのを待たなければならないため、これは非常に平行していないように見えます$ .when()メソッド。
アンソニーマクリン2015

@AnthonyMcLin:これはthen()、同様の方法で呼び出しをチェーンする場合とは異なり、より優れたパフォーマンス(ほとんどの非同期コーディングでは無視できる)ではなく、コーディングのより単純な代替を提供することを目的としています。との動作$.whenは、それが並列である(連鎖されていない)ように動作することです。それが機能するので、有用な代替品を捨てる前にそれを試してください:)
Gone Coding

2
@Alnitak:コース用の馬。あなたは確かに意見を得る資格がありますが、あなた自身はこれを明らかに使用していません。私の意見は、この手法の実際の使用に基づいています。それは機能し、用途があります。そのため、「警告のロード」(1つ)や「何も解決しない」(真ではない)などの誇張に基づいてツールボックスからツールを破棄する理由は、配列処理を排除し、戻り値がどこにある場合の並列プロミスの連鎖を簡素化するかです。値は必要ありません。これは、ご存じのとおり、いずれにしても並列処理のケースではめったに使用されません)。反対票は「この回答は役に立たない」ためのものであることになっています:)
Gone Coding

1
@GoneCoding様 投票の解説を回答に追加しないでください。これはコメントには適していますが、それ以外の点では、他の点では優れたコンテンツの邪魔になるノイズです。ありがとう。
16年

1
@halfer:私はこれ以上投稿しませんが、オリジナルに表示された無知にイライラします。最近、すべての新しいアイデアを自分に任せています:)
コーディング終了

4

私は$ .eachを使用して他のものを提案したいと思います:

  1. 次のようにajax関数を宣言します。

    function ajaxFn(someData) {
        this.someData = someData;
        var that = this;
        return function () {
            var promise = $.Deferred();
            $.ajax({
                method: "POST",
                url: "url",
                data: that.someData,
                success: function(data) {
                    promise.resolve(data);
                },
                error: function(data) {
                    promise.reject(data);
                }
            })
            return promise;
        }
    }
  2. 送信するajaxで関数の配列を作成するコードの一部:

    var arrayOfFn = [];
    for (var i = 0; i < someDataArray.length; i++) {
        var ajaxFnForArray = new ajaxFn(someDataArray[i]);
        arrayOfFn.push(ajaxFnForArray);
    }
  3. そして、ajaxを送信して関数を呼び出す:

    $.when(
        $.each(arrayOfFn, function(index, value) {
            value.call()
        })
    ).then(function() {
            alert("Cheer!");
        }
    )

1

トランスパイルしていてES6にアクセスできる場合は、オブジェクトの反復可能な各項目を個別の引数として具体的に適用するスプレッド構文を使用できます$.when()

$.when(...deferreds).done(() => {
    // do stuff
});

MDNリンク-スプレッド構文


0

angularJSまたはQ promiseライブラリのバリアントを使用している.all()場合、この正確な問題を解決する方法があります。

var savePromises = [];
angular.forEach(models, function(model){
  savePromises.push(
    model.saveToServer()
  )
});

$q.all(savePromises).then(
  function success(results){...},
  function failed(results){...}
);

完全なAPIを見る:

https://github.com/kriskowal/q/wiki/API-Reference#promiseall

https://docs.angularjs.org/api/ng/service/$q


4
これはまったく無関係です。
Benjamin Gruenbaum 2015

@BenjaminGruenbaumどうですか?すべてのjavascript promiseライブラリは同様のAPIを共有しており、さまざまな実装を表示しても何も問題はありません。私はangularの答えを探してこのページに到達しました、そして私は他の多くのユーザーがこのページに到達し、必ずしもjqueryのみの環境にいるとは限らないと思います。
mastaBlasta

2
つまり、jQueryのpromise このAPIを共有しないため、Stack Overflowの回答としてこれは完全に不適切です。Angularにも同様の回答があり、そこで質問できます。(言うまでもなく、あなたは.mapここにいるべきですが、まあ)。
Benjamin Gruenbaum 2015

0

各ループに投稿し、ajaxから受け取った数値からいくつかのフィールドにhtmlマークアップを設定する非常に似たケースがありました。次に、これらのフィールドの(更新された)値の合計を計算し、合計フィールドに配置する必要がありました。

したがって、問題は、すべての数値を合計しようとしたが、非同期ajax呼び出しからまだデータが戻っていないことでした。コードを再利用するには、いくつかの関数でこの機能を完了する必要がありました。私の外部関数はデータを待ってから、完全に更新されたDOMを使用していくつかの処理を行います。

    // 1st
    function Outer() {
        var deferreds = GetAllData();

        $.when.apply($, deferreds).done(function () {
            // now you can do whatever you want with the updated page
        });
    }

    // 2nd
    function GetAllData() {
        var deferreds = [];
        $('.calculatedField').each(function (data) {
            deferreds.push(GetIndividualData($(this)));
        });
        return deferreds;
    }

    // 3rd
    function GetIndividualData(item) {
        var def = new $.Deferred();
        $.post('@Url.Action("GetData")', function (data) {
            item.html(data.valueFromAjax);
            def.resolve(data);
        });
        return def;
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.