Ertelenmişler dizisine $ .when () öğesine iletin


447

Neler olup bittiğine dair tartışmalı bir örnek: 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>");
        });
    });
});

"Her şey bitti!" ertelenen görevler tamamlandıktan sonra görünecek, ancak $.when()bir dizi Ertelenmiş nesnenin nasıl işleneceğini bilmiyor gibi görünecektir. "Hepsi tamam!" dizi ertelenen bir nesne olmadığından ilk önce gerçekleşir, bu nedenle jQuery devam eder ve yeni yapıldığını varsayar.

Biri gibi fonksiyonlar içine nesneleri geçebilir biliyorum $.when(deferred1, deferred2, ..., deferredX)ama çözmeye çalışıyorum gerçek sorun kaç Yürütülecek nesneleri olacak bilinmemektedir.



Aşağıya bu çok eski soru için yeni, daha basit bir cevap eklendi. Sen do not bir dizi kullanmak gerekir veya $.when.applyhiç aynı sonucu elde etmek için.
Gone Coding

çok özel olduğu için soru konusunu geri
aldık

Yanıtlar:


732

Normalde ayrı parametreler olmasını bekleyen herhangi bir işleve bir değer dizisi iletmek için şunu kullanın Function.prototype.apply: bu durumda aşağıdakilere ihtiyacınız vardır:

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

Bkz. Http://jsfiddle.net/YNGcm/21/

ES6'da forma ... operatörünü kullanabilirsiniz :

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

Her iki durumda da, .thenişleyicinin kaç tane resmi parametre gerektireceğini önceden bilmeniz pek olası olmadığından , bu işleyicinin argumentsher bir vaadin sonucunu alabilmesi için diziyi işlemesi gerekir .


4
Harika çalışıyor. :) Google aracılığıyla bu kadar basit bir değişiklik yapamadığım için şaşırdım!
adamjford

9
o bir jenerik yöntem değil belirli oldugu için bilmiyor $.when- f.apply(ctx, my_array)arayacak file this == ctxve ayarlı argümanlar içerikleri arasında my_array.
Alnitak

4
@Alnitak: Şu an JavaScript'i ne kadar süredir yazdığımı göz önünde bulundurarak bu yöntemi bilmediğim için utanıyorum!
adamjford

5
FWIW, geçen tartışma ile bir earler soruya Eli yanıtında bağlantı $vs nullilk parametre olarak bir okuma değer. Ancak bu özel durumda önemli değil.
Alnitak

4
@Alnitak: Evet, ancak $daha az yazıyor nullve $.whenuygulama değiştiğinde güvende olursunuz (bu durumda muhtemelen değil, neden thisvarsayılan olarak değişmeden kalmıyorsunuz ).
Tomasz Zieliński

109

Yukarıdaki geçici çözümler (teşekkürler!), resolve()JQuery ertelemenin yöntemine sağlanan nesneleri geri alma sorununu düzgün bir şekilde gidermez, çünkü jQuery bir dizi yerine bireysel parametrelerle çağırır done()ve fail()geri çağırır . Bu arguments, ertelenmiş dizinin döndürdüğü tüm çözümlenmiş / reddedilen nesneleri çirkin almak için sözde diziyi kullanmamız gerektiği anlamına gelir :

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

Bir erteleme dizisinden geçtiğimizden, bir dizi sonucu geri almak güzel olurdu. Ayrıca, sözde yöntemler kullanabilmemiz için sözde dizi yerine gerçek bir dizi geri almak da iyi olurdu Array.sort().

İşte esinlenerek bir çözümdür when.js 'ın when.all()yöntemine adresleri bu sorunların olduğu:

// 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)]);
                });
        });
    }
}

Şimdi sadece bir dizi erteleme / vaatler geçirebilir ve geri aramanızdaki bir dizi çözülmüş / reddedilen nesne dizisini geri alabilirsiniz, örneğin:

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

6
Sadece bu yüzden 'bu' deferred.resolveWith aynı orijinal deferreds olsun resolveWith ve rejectWith kullanmak isteyebilirsiniz (bu, [Array.prototype.slice.call (argümanlar)]) vb
Jamie Pate

1
Kodunuzla ilgili küçük bir sorun var, dizide yalnızca bir öğe olduğunda, sonuç dizisi, tek bir öğeye sahip bir dizi (bir diziyi bekleyen kodu kıracak) yerine yalnızca bu sonucu döndürür. Düzeltmek için var toArray = function (args) { return deferreds.length > 1 ? $.makeArray(args) : [args]; }bunun yerine bu işlevi kullanın Array.prototype.slice.call.
Luan Nico

Hm, bu 404'leri yakalamıyor gibi görünüyor.
t.mikael.d

Sebebini buldu, .fail bunun yerine .reject olmalı - böylece 404'leri yakalayabilir.
t.mikael.d

38

whenYöntemi dizinize uygulayabilirsiniz :

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

$.when.apply($, arr);

Bir dizi jQuery Deferreds ile nasıl çalışıyorsunuz?


Aslında bu soruyu gördüm ama sanırım bu sorudaki tüm ekstra detaylar sorunumun cevabının (tam orada) başımın üzerinden uçmasına neden oldu.
adamjford

1
@adamjford, kendinizi daha iyi hissettirirse, sorunuzu tüketmeyi daha kolay buldum (ve önce bu kesin sorun için Google aramalarında).
patridge

@patridge: Size yardımcı olduğunu duyduğum için mutluyum!
adamjford

Bu harika bir cevap, ama bunun orijinal sorudaki örneğe nasıl uygulandığı bana açık değildi. Bağlantılı soruyu inceledikten sonra, "$ .when (ertelenmiş) .done (function () {" satırının basitçe "$ .when.apply ($, ertelenmiş) .done (function () { " Doğru.?
Garland Papa

7

Birden çok paralel AJAX çağrısını çağırırken, ilgili yanıtları işlemek için iki seçeneğiniz vardır.

  1. Senkron AJAX çağrısını / birbiri ardına kullanın / önerilmez
  2. Kullanım Promises'dizi ve $.whenhangi kabul promises ve geri .donetüm çağrılan alır promises ilgili yanıtları ile başarılı bir şekilde geri dönüş vardır.

Misal

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
cevabınız fazla geldi ve soru başlığında yaptığınız düzenleme de öyle. OP, AJAX çağrılarının nasıl yapılacağını ve ertelenmiş nesnelerin bir dizisini nasıl alacağını zaten biliyordu. Tek sorunun noktası bu dizi geçirilecek oldu $.when.
Alnitak

5
Örnekle ayrıntılı olarak açıklamanın mevcut seçeneklerle daha iyi olacağını düşündüm. Ve bunun için downvote'un gerekli olduğunu düşünmüyorum.
vinayakj

2
aşağı oy 1. için bile eşitleme (değil bir öneri de olsa) önermek için 2. 2. ( for ... inbir dizi dahil ?!) örneklerde düşük kaliteli kod
Alnitak

1
1. Kabul etti, sahip olmalıydı (not recommended)2.Kabul değil - for ... intamam çünkü dizi sadece gereken özellikleri içeriyor (ekstra özellik yok). teşekkürler yine de
vinayakj

1
re: 2 - sorun, bu garantiyi veremeyen veya eklemek için yeterince aptal olan diğer insanlar tarafından kopyalanabilir Array.prototype. Her halükarda, performansı kritik olmayan kodlar için, / loop .mapyerine kullanmak daha iyi olur , örneğin - yapılan iş. forpushvar promises = capitalCities.map(ajaxRequest); $.when.apply($, promises).then(fillCountryCapitals)
Alnitak

6

Basit bir alternatif olarak, gerektirmeyen $.when.applyveya bir array, birden fazla paralel vaat için tek bir vaat üretmek için aşağıdaki deseni kullanabilirsiniz:

promise = $.when(promise, anotherPromise);

Örneğin

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>");
        });
    });
});

Notlar:

  • Bunu birisinin zincirinin sıralı olarak vaat ettiğini gördükten sonra promise = promise.then(newpromise)
  • Dezavantajı, sahnelerin arkasında ekstra vaat nesneleri oluşturması ve sonunda iletilen parametrelerin çok yararlı olmamasıdır (ek nesnelerin içine yerleştirildikleri için). İstediğiniz için kısa ve basit olsa da.
  • Üstte dizi veya dizi yönetimi gerektirmez.

2
Yanılıyorsam beni düzeltin, ama yaklaşımınız etkili bir şekilde $ .when ($ .when ($ .when (...))) iç içe geçiriyorsa, 10 yineleme varsa tekrarlanan 10 seviye derinliğinde sonuçlanır. Her seviyenin bir çocuğun kendi vaadini vermeden önce iç içe vaat etmesini beklemek zorunda olduğunuz için bu çok paralel görünmüyor - Bence kabul edilen cevaptaki dizi yaklaşımı, yerleşik esnek parametre davranışını kullandığından çok daha temiz $ .when () yöntemi.
Anthony McLin

@AnthonyMcLin: Bu, then()çağrıları benzer şekilde zincirlemede olduğu gibi, daha iyi performans (çoğu Async kodlaması ile ihmal edilebilir) değil, kodlamaya daha basit bir alternatif sunmayı amaçlamaktadır . İle davranışı $.whenparalel (zincirleme değil) gibi davranmaktır. Lütfen işe yaradığı için yararlı bir alternatif atmadan önce deneyin :)
Gone Coding

2
@Alnitak: Kurslar için atlar. Kesinlikle bir görüş hakkına sahipsiniz, ancak bunu açıkça kendiniz kullanmadınız. Kendi fikrim bu tekniğin pratik kullanımlarına dayanıyor. Bu işleri ve kullanım alanı vardır, öyleyse neden "uyarılar bir sürü" gibi abartılar dayalı araç kutusundan bir araç dışarı atmak (bir) ve "çözer şey" (doğru değil - bu dizi işlem ve paralel sözler zincirleme kolaylık imkanına dönüş ortadan kaldırır Bilmeniz gerektiği gibi, zaten paralel işleme durumlarında nadiren kullanılan değerlere gerek yoktur). Downvotes için "bu cevap yararlı değil" olması gerekiyor :)
Gone Coding

1
Merhaba @GoneCoding. Cevaplarınıza oylama yorumu eklememenizi isteyebilir miyim? Bu yorumlar için uygundur, ancak aksi takdirde iyi içerikten uzaklaşan gürültüdür. Teşekkürler.
halfer

1
@halfer: Artık yayınlamıyorum ama orijinal bir şeye gösterilen cehaletten rahatsızım. Bugünlerde tüm yeni fikirleri kendime saklıyorum :)
Gone Coding

4

$ .Each kullanarak başka bir teklif etmek istiyorum:

  1. Ajax fonksiyonunu şöyle beyan edebiliriz:

    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. Gönderilecek ajax ile fonksiyon dizisi oluşturduğumuz kodun bir kısmı:

    var arrayOfFn = [];
    for (var i = 0; i < someDataArray.length; i++) {
        var ajaxFnForArray = new ajaxFn(someDataArray[i]);
        arrayOfFn.push(ajaxFnForArray);
    }
  3. Ve ajax göndererek işlevleri çağırmak:

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

1

Aktarıyorsanız ve ES6'ya erişiminiz varsa, bir nesnenin yinelenebilir öğelerinin her birini ayrı bir bağımsız değişken olarak, tam da $.when()ihtiyaç duyduğu şekilde uygulayan yayılmış sözdizimini kullanabilirsiniz .

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

MDN Bağlantısı - Spread Sözdizimi


0

AngularJS veya Q promise kütüphanesinin bir varyantını kullanıyorsanız, .all()bu tam sorunu çözen bir yönteminiz vardır.

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

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

tam API'ya bakın:

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

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


4
Bu tamamen alakasız.
Benjamin Gruenbaum

@BenjaminGruenbaum Nasıl? Tüm javascript vaat kütüphaneleri benzer bir API'yı paylaşır ve farklı uygulamaları göstermeyle ilgili bir sorun yoktur. Açısal bir cevap arayan bu sayfaya ulaştım ve diğer birçok kullanıcının bu sayfaya ulaşacağından ve yalnızca jquery ortamında bulunmadığından şüpheleniyorum.
mastaBlasta

2
JQuery'nin vaat çünkü Yani, yok bu API paylaşan, bu Yığın taşması cevap olarak tamamen uygunsuz olduğu - orada açısal için benzer cevaplar ve orada sorabilirsiniz. (Bahsetmiyorum, .mapburada olmalısın ama oh iyi).
Benjamin Gruenbaum

0

Ben her döngüde gönderme ve daha sonra ajax alınan numaralardan bazı alanlarda html işaretleme ayarlama çok benzer bir durum vardı. Daha sonra bu alanların (şimdi güncellenmiş) değerlerinin toplamını ve toplam alana yerleştirmem gerekiyordu.

Böylece sorun, tüm numaralar için bir miktar yapmaya çalışıyordum ama async ajax çağrılarından henüz hiçbir veri geri gelmemişti. Kodu yeniden kullanabilmek için bu işlevi birkaç işlevde tamamlamam gerekiyordu. Dış fonksiyonum, daha sonra tamamen güncellenmiş DOM ile bazı şeyler yapmadan önce verileri bekliyor.

    // 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;
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.