Lulus dalam array Ditangguhkan ke $ .when ()

447

Berikut adalah contoh yang dibuat-buat tentang apa yang terjadi: 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>");
        });
    });
});

Saya ingin "Semua selesai!" muncul setelah semua tugas yang ditangguhkan selesai, tetapi $.when()tampaknya tidak tahu bagaimana menangani array objek yang Ditangguhkan. "Semua selesai!" terjadi pertama kali karena array bukan objek yang ditangguhkan, jadi jQuery berjalan di depan dan menganggap itu baru saja selesai.

Saya tahu orang bisa meneruskan objek ke fungsi seperti $.when(deferred1, deferred2, ..., deferredX)tetapi tidak diketahui berapa banyak objek yang ditangguhkan akan ada di eksekusi dalam masalah aktual yang saya coba selesaikan.

adamjford
sumber
Menambahkan jawaban baru, lebih sederhana, untuk pertanyaan yang sangat lama di bawah ini. Anda tidak perlu menggunakan array atau $.when.applysama sekali untuk mendapatkan hasil yang sama.
Hilang Coding
memutar kembali subjek pertanyaan, karena terlalu spesifik (ini bukan hanya masalah AJAX)
Alnitak

Jawaban:

732

Untuk meneruskan array nilai ke fungsi apa pun yang biasanya mengharapkannya sebagai parameter yang terpisah, gunakan Function.prototype.apply, jadi dalam hal ini Anda perlu:

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

Lihat http://jsfiddle.net/YNGcm/21/

Di ES6, Anda bisa menggunakan ... operator spread sebagai gantinya:

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

Dalam kedua kasus tersebut, karena tidak mungkin Anda akan tahu sebelumnya berapa banyak parameter formal yang diperlukan .thenpawang, pawang itu perlu memproses argumentsarray untuk mengambil hasil dari setiap janji.

Alnitak
sumber
4
Ini berhasil, luar biasa. :) Saya kagum saya tidak bisa mengeruk perubahan sederhana melalui Google!
adamjford
9
itu karena itu adalah metode generik, tidak spesifik untuk $.when- f.apply(ctx, my_array)akan memanggil fdengan this == ctxdan argumen diatur ke isi dari my_array.
Alnitak
4
@Alnitak: Saya agak malu karena saya tidak tahu tentang metode itu, mengingat sudah berapa lama saya menulis JavaScript sekarang!
adamjford
5
FWIW, tautan dalam jawaban Eli ke pertanyaan sebelumnya dengan diskusi passing $vs nullsebagai parameter pertama layak dibaca. Dalam kasus khusus ini, tidak masalah.
Alnitak
4
@Alnitak: Ya, tetapi $kurang mengetik daripada nulldan Anda aman saat $.whenimplementasi berubah (bukan karena kemungkinan dalam kasus ini tetapi mengapa tidak thisdiubah secara default).
Tomasz Zieliński
109

Penanganan masalah di atas (terima kasih!) Tidak dengan benar mengatasi masalah mendapatkan kembali objek yang disediakan untuk metode yang ditangguhkan resolve()karena jQuery memanggil done()dan memanggil kembali fail()dengan parameter individual, bukan array. Itu berarti kita harus menggunakan argumentspseudo-array untuk mendapatkan semua objek yang diselesaikan / ditolak dikembalikan oleh array yang ditangguhkan, yang jelek:

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

Karena kami meneruskan dalam array yang ditangguhkan, alangkah baiknya untuk mendapatkan kembali array hasil. Akan lebih baik untuk mendapatkan kembali array aktual alih-alih pseudo-array sehingga kita dapat menggunakan metode seperti Array.sort().

Berikut ini adalah solusi terinspirasi oleh when.js 's when.all()metode yang alamat masalah ini:

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

Sekarang Anda dapat dengan mudah memasukkan array dari ditangguhkan / janji dan mendapatkan kembali array objek yang diselesaikan / ditolak dalam panggilan balik Anda, seperti:

$.when.all(deferreds).then(function(objects) {
    console.log("Resolved objects:", objects);
});
crispyduck
sumber
6
Anda mungkin ingin menggunakan resolWith dan menolakWith sehingga Anda mendapatkan ditangguhkan orisinal yang sama dengan 'this' ditangguhkan.resolveWith (ini, [Array.prototype.slice.call (argumen)]) dll
Jamie Pate
1
Hanya ada masalah kecil dengan kode Anda, ketika hanya ada satu elemen dalam array, array hasil mengembalikan hanya hasil itu, bukan array dengan elemen tunggal (yang akan memecahkan kode yang mengharapkan array). Untuk memperbaikinya, gunakan fungsi ini var toArray = function (args) { return deferreds.length > 1 ? $.makeArray(args) : [args]; }sebagai ganti Array.prototype.slice.call.
Luan Nico
Hm, ini sepertinya tidak menangkap 404.
t.mikael.d
Ditemukan alasannya,. Gagal seharusnya. Menolak sebagai gantinya - sehingga dapat menangkap 404.
t.mikael.d
38

Anda dapat menerapkan whenmetode ini ke array Anda:

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

$.when.apply($, arr);

Bagaimana Anda bekerja dengan array jQuery Deferreds?

Eli
sumber
Saya benar-benar melihat pertanyaan itu tetapi saya kira semua detail tambahan dalam pertanyaan itu menyebabkan jawaban untuk masalah saya (yang ada di sana) untuk terbang tepat di atas kepala saya.
adamjford
1
@adamjford, jika itu membuat Anda merasa lebih baik, saya menemukan pertanyaan Anda lebih mudah untuk dikonsumsi (dan pertama di pencarian khusus Google saya untuk masalah ini).
patridge
@ Patridge: Senang mendengarnya membantu Anda!
adamjford
Ini adalah jawaban yang bagus, tetapi tidak jelas bagi saya bagaimana ini diterapkan pada contoh dalam pertanyaan awal. Setelah berkonsultasi dengan pertanyaan yang ditautkan, menjadi jelas bahwa baris "$ .when (ditangguhkan) .done (function () {" harus diubah menjadi "$ .when.apply ($, ditangguhkan) .done (function () { ". Benar?
Garland Paus
7

Saat memanggil beberapa panggilan AJAX paralel, Anda memiliki dua opsi untuk menangani masing-masing respons.

  1. Gunakan panggilan AJAX Sinkron / satu demi satu / tidak disarankan
  2. Gunakan Promises'larik dan $.whenyang menerima promises dan panggilan .donebaliknya dipanggil ketika semua promises berhasil dikembalikan dengan tanggapan masing-masing.

Contoh

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>

vinayakj
sumber
1
jawaban Anda melampaui batas, dan begitu pula hasil edit Anda dengan judul pertanyaan. OP sudah tahu cara melakukan panggilan AJAX dan mendapatkan berbagai objek yang ditangguhkan. Satu- satunya titik pertanyaan adalah bagaimana meneruskan array itu ke $.when.
Alnitak
5
Saya pikir menjelaskan secara rinci dengan contoh akan lebih baik, dengan opsi yang tersedia. Dan untuk itu saya tidak berpikir downvote diperlukan.
vinayakj
2
downvote adalah untuk 1. bahkan menyarankan sinkronisasi (walaupun dengan rekomendasi untuk tidak) 2. kode berkualitas buruk dalam contoh (termasuk for ... inpada array ?!)
Alnitak
1
1. Setuju, seharusnya sudah (not recommended)2.Tidak setuju - for ... inok karena array hanya berisi properti-properti yang perlu (tidak ada properti tambahan). thanx anyways
vinayakj
1
re: 2 - masalahnya adalah mungkin disalin oleh orang lain yang tidak dapat membuat jaminan itu, atau telah cukup bodoh untuk ditambahkan Array.prototype. Dalam hal apa pun, untuk kode non-kinerja-kritis akan lebih baik digunakan .mapdaripada for/ pushloop, misalnya var promises = capitalCities.map(ajaxRequest); $.when.apply($, promises).then(fillCountryCapitals)- pekerjaan selesai.
Alnitak
6

Sebagai alternatif sederhana, yang tidak memerlukan $.when.applyatau array, Anda dapat menggunakan pola berikut untuk menghasilkan janji tunggal untuk banyak janji paralel:

promise = $.when(promise, anotherPromise);

misalnya

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

Catatan:

  • Saya menemukan yang ini setelah melihat rantai seseorang menjanjikan secara berurutan, menggunakan promise = promise.then(newpromise)
  • Kelemahannya adalah ia menciptakan objek janji ekstra di belakang layar dan parameter apa pun yang diberikan pada akhirnya tidak terlalu berguna (karena mereka bersarang di dalam objek tambahan). Untuk apa yang Anda inginkan, meskipun pendek dan sederhana.
  • Sisi baiknya adalah tidak memerlukan manajemen array atau array.
Goding Coding
sumber
2
Koreksi saya jika saya salah, tetapi pendekatan Anda secara efektif menyarangkan $ .when ($ .when ($ .when (...))) sehingga Anda akhirnya secara berulang menyarangkan 10 level jika ada 10 iterasi. Ini tampaknya tidak paralel karena Anda harus menunggu setiap tingkat untuk mengembalikan janji yang bersarang anak sebelum dapat mengembalikan janjinya sendiri - Saya pikir pendekatan array dalam jawaban yang diterima jauh lebih bersih karena menggunakan perilaku parameter fleksibel yang dibangun ke dalam metode $ .when ().
Anthony McLin
@AnthonyMcLin: ini dimaksudkan untuk memberikan alternatif yang lebih sederhana untuk pengkodean, bukan kinerja yang lebih baik (yang dapat diabaikan dengan sebagian besar pengkodean Async), seperti yang dilakukan dengan chaining then()panggilan dengan cara yang sama. Perilaku dengan $.whenadalah bertindak seperti itu paralel (tidak dirantai). Silakan coba sebelum membuang alternatif yang berguna karena tidak bekerja :)
Hilang Coding
2
@Alnitak: Kuda untuk kursus. Anda tentu berhak mendapat pendapat, tetapi Anda jelas tidak menggunakannya sendiri. Pendapat saya sendiri didasarkan pada penggunaan praktis dari teknik ini. Ini bekerja dan memiliki kegunaan, jadi mengapa membuang alat dari kotak alat berdasarkan pada berlebihan seperti "banyak peringatan" (satu) dan "tidak menyelesaikan apa-apa" (tidak benar - itu menghilangkan pemrosesan array dan menyederhanakan rantai janji paralel di mana pengembalian nilai-nilai tidak perlu, yang seperti yang Anda ketahui jarang digunakan dalam kasus-kasus pemrosesan paralel). Downvotes seharusnya untuk "jawaban ini tidak berguna" :)
Hilang Coding
1
Hai @GoneCoding. Bolehkah saya meminta Anda untuk tidak menambahkan komentar untuk jawaban Anda? Itu cocok untuk komentar, tetapi selain itu kebisingan yang mengalihkan perhatian dari konten yang baik. Terima kasih.
halfer
1
@halfer: Saya tidak memposting lagi tapi saya kesal dengan ketidaktahuan yang ditampilkan untuk sesuatu yang asli. Menyimpan semua ide baru untuk diriku sendiri saat ini :)
Hilang Coding
4

Saya ingin mengusulkan yang lain dengan menggunakan $ .each:

  1. Kami dapat mendeklarasikan fungsi ajax seperti:

    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. Bagian dari kode tempat kami membuat berbagai fungsi dengan ajax untuk dikirim:

    var arrayOfFn = [];
    for (var i = 0; i < someDataArray.length; i++) {
        var ajaxFnForArray = new ajaxFn(someDataArray[i]);
        arrayOfFn.push(ajaxFnForArray);
    }
  3. Dan memanggil fungsi dengan mengirim ajax:

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

Jika Anda mentransisikan dan memiliki akses ke ES6, Anda dapat menggunakan sintaksis spread yang secara khusus menerapkan setiap item yang dapat diulang dari suatu objek sebagai argumen diskrit, seperti yang $.when()dibutuhkannya.

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

MDN Link - Sebarkan Sintaks

peninggalan
sumber
0

Jika Anda menggunakan angularJS atau varian pustaka janji Q, maka Anda memiliki .all()metode yang memecahkan masalah ini.

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

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

lihat API lengkap:

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

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

mastaBlasta
sumber
4
Ini sama sekali tidak relevan.
Benjamin Gruenbaum
@BenjaminGruenbaum Bagaimana bisa begitu? Semua perpustakaan janji javascript berbagi API yang sama, dan tidak ada yang salah dengan menunjukkan implementasi yang berbeda. Saya mencapai halaman ini mencari jawaban untuk sudut, dan saya curiga banyak pengguna lain akan mencapai halaman ini dan tidak harus berada di lingkungan jquery saja.
mastaBlasta
2
Yaitu, karena janji jQuery tidak membagikan API ini, ini sama sekali tidak pantas sebagai jawaban di Stack Overflow - ada jawaban serupa untuk Angular dan Anda dapat bertanya di sana. (Belum lagi, Anda harus di .mapsini tapi oh well).
Benjamin Gruenbaum
0

Saya memiliki kasus yang sangat mirip di mana saya memposting di setiap loop dan kemudian mengatur markup html di beberapa bidang dari nomor yang diterima dari ajax. Saya kemudian perlu melakukan penjumlahan dari nilai-nilai (sekarang diperbarui) dari bidang-bidang ini dan tempatkan di bidang total.

Jadi masalahnya adalah saya mencoba melakukan penjumlahan pada semua nomor tetapi belum ada data yang kembali dari panggilan ajax async. Saya perlu menyelesaikan fungsi ini dalam beberapa fungsi untuk dapat menggunakan kembali kode. Fungsi luar saya menunggu data sebelum saya pergi dan melakukan beberapa hal dengan DOM yang sepenuhnya diperbarui.

    // 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;
    }
Cameron Forward
sumber