jQuery callback untuk beberapa panggilan ajax

132

Saya ingin membuat tiga panggilan ajax dalam acara klik. Setiap panggilan ajax melakukan operasi yang berbeda dan mengembalikan data yang diperlukan untuk panggilan balik akhir. Panggilan itu sendiri tidak tergantung satu sama lain, mereka semua bisa pergi pada saat yang sama, namun saya ingin memiliki panggilan balik terakhir ketika ketiganya selesai.

$('#button').click(function() {
    fun1();
    fun2();
    fun3();
//now do something else when the requests have done their 'success' callbacks.
});

var fun1= (function() {
    $.ajax({/*code*/});
});
var fun2 = (function() {
    $.ajax({/*code*/});
});
var fun3 = (function() {
    $.ajax({/*code*/});
});
MisterIsaak
sumber

Jawaban:

112

Ini adalah objek callback yang saya tulis di mana Anda bisa mengatur satu callback untuk diaktifkan setelah semuanya selesai atau membiarkan masing-masing memiliki callback mereka sendiri dan memecat mereka semua setelah semuanya selesai:

MEMPERHATIKAN

Karena jQuery 1.5+ Anda dapat menggunakan metode tangguhan seperti dijelaskan dalam jawaban lain:

  $.when($.ajax(), [...]).then(function(results){},[...]);

Contoh ditangguhkan di sini

untuk jQuery <1.5 berikut ini akan berfungsi atau jika Anda perlu panggilan ajax Anda dipecat pada waktu yang tidak diketahui seperti yang ditunjukkan di sini dengan dua tombol: dipecat setelah kedua tombol diklik

[pemakaian]

untuk panggilan balik tunggal setelah selesai: Contoh Kerja

// initialize here
var requestCallback = new MyRequestsCompleted({
    numRequest: 3,
    singleCallback: function(){
        alert( "I'm the callback");
    }
});

//usage in request
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.requestComplete(true);
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.requestComplete(true);
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.requestComplete(true);
    }
});

masing-masing memiliki panggilan balik sendiri ketika semuanya selesai: Contoh Kerja

//initialize 
var requestCallback = new MyRequestsCompleted({
    numRequest: 3
});

//usage in request
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.addCallbackToQueue(true, function() {
            alert('Im the first callback');
        });
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.addCallbackToQueue(true, function() {
            alert('Im the second callback');
        });
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.addCallbackToQueue(true, function() {
            alert('Im the third callback');
        });
    }
});

[Kode]

var MyRequestsCompleted = (function() {
    var numRequestToComplete, requestsCompleted, callBacks, singleCallBack;

    return function(options) {
        if (!options) options = {};

        numRequestToComplete = options.numRequest || 0;
        requestsCompleted = options.requestsCompleted || 0;
        callBacks = [];
        var fireCallbacks = function() {
            alert("we're all complete");
            for (var i = 0; i < callBacks.length; i++) callBacks[i]();
        };
        if (options.singleCallback) callBacks.push(options.singleCallback);

        this.addCallbackToQueue = function(isComplete, callback) {
            if (isComplete) requestsCompleted++;
            if (callback) callBacks.push(callback);
            if (requestsCompleted == numRequestToComplete) fireCallbacks();
        };
        this.requestComplete = function(isComplete) {
            if (isComplete) requestsCompleted++;
            if (requestsCompleted == numRequestToComplete) fireCallbacks();
        };
        this.setCallback = function(callback) {
            callBacks.push(callBack);
        };
    };
})();
Subhaze
sumber
1
Terima kasih atas contoh kode yang hebat! Saya pikir saya akan bisa tersandung melalui sisanya. Terima kasih lagi!
MisterIsaak
Tidak masalah, senang itu membantu! Saya agak terbawa dengan itu: P masih punya beberapa ide untuk itu. Saya berencana untuk memperbaruinya ke tempat Anda tidak perlu menentukan sejumlah permintaan pada inisialisasi.
Subhaze
Bagus, kuharap begitu! Sangat menarik, saya sarankan melempar opsi untuk menambahkan panggilan balik tambahan. Saya melakukannya dan itu berfungsi sempurna untuk apa yang ingin saya lakukan. Terima kasih lagi!
MisterIsaak
Saya lakukan tetapi lupa menambahkannya ke kasus penggunaan: P gunakan setCallbackuntuk menambahkan lebih banyak ke antrian. Meskipun sekarang saya berpikir tentang itu ... Saya harus menyebutnya addCallbackatau semacamnya ... dan tidak hanya mengatur karena itu menambah antrian
subhaze
Apakah singleCallbackvariabel pada baris ke-2 tidak perlu? Atau apakah saya melewatkan sesuatu?
nimcap
158

Sepertinya Anda sudah mendapatkan beberapa jawaban untuk ini, namun saya pikir ada sesuatu yang layak disebutkan di sini yang akan sangat menyederhanakan kode Anda. jQuery memperkenalkan $.whendi v1.5. Sepertinya:

$.when($.ajax(...), $.ajax(...)).then(function (resp1, resp2) {
    //this callback will be fired once all ajax calls have finished.
});

Tidak melihatnya disebutkan di sini, semoga membantu.

Greg
sumber
6
Terima kasih atas jawabannya, ini jelas cara untuk saya. menggunakan jQuery, ringkas, pendek, dan ekspresif.
nimcap
2
Jawaban Subhaze bagus tapi sungguh, ini yang benar.
Molomby
9
Saya setuju ini adalah solusi yang baik ketika Anda memiliki jQuery 1.5+ tetapi pada saat jawaban, fungsionalitas yang ditangguhkan tidak tersedia. :(
Subhaze
13

Tidak melihat kebutuhan untuk objek yang jelek sendiri. Sederhana memiliki variabel yang merupakan bilangan bulat. Saat Anda memulai permintaan, tambahkan nomornya. Ketika seseorang selesai, kurangi itu. Saat nol, tidak ada permintaan yang sedang berlangsung, jadi Anda selesai.

$('#button').click(function() {
    var inProgress = 0;

    function handleBefore() {
        inProgress++;
    };

    function handleComplete() {
        if (!--inProgress) {
            // do what's in here when all requests have completed.
        }
    };

    $.ajax({
        beforeSend: handleBefore,
        complete: function () {
            // whatever
            handleComplete();
            // whatever
        }
    });
    $.ajax({
        beforeSend: handleBefore,
        complete: function () {
            // whatever
            handleComplete();
            // whatever
        }
    });
    $.ajax({
        beforeSend: handleBefore,
        complete: function () {
            // whatever
            handleComplete();
            // whatever
        }
    });
});
Mat
sumber
Ini sangat sederhana dan berfungsi dengan baik ... Maukah Anda menjelaskan sedikit lebih baik apa yang terjadi jika (--in Kemajuan)? Seharusnya mengontrol jika variabel inProgress == 0 dan kurangi satu unit untuk itu tapi saya tidak mendapatkan sintaks kompak ...
Pitto
1
@ Pitto: Ups ... sebenarnya, ada kesalahan logika di sana, dan memang seharusnya begitu if (!--inProgress). Versi yang benar analog dengan inProgress = inProgress - 1; if (inProgress == 0) { /* do stuff */ }. Bentuk ringkas menggabungkan operator penurunan awalan ( --) dan operator negasi ( !) untuk mengevaluasi ke "true" (dan karenanya mengeksekusi ifblok), jika inProgresshasil decrementing dalam inProgress == 0, dan mengevaluasi ke "false" (dan karena itu tidak melakukan apa-apa) sebaliknya.
Matt
Cukup menyedihkan! Terima kasih atas waktu dan kesabaran Anda.
Pitto
@ Pitto: Humm .. bisakah Anda menautkan ke kode yang Anda gunakan? Ini bekerja untuk saya .
Matt
10

Perlu dicatat bahwa karena $.whenmengharapkan semua permintaan ajax sebagai argumen berurutan (bukan array), Anda biasanya akan melihat $.whendigunakan dengan .apply()seperti:

// Save all requests in an array of jqXHR objects
var requests = arrayOfThings.map(function(thing) {
    return $.ajax({
        method: 'GET',
        url: 'thing/' + thing.id
    });
});
$.when.apply(this, requests).then(function(resp1, resp2/*, ... */) {
    // Each argument is an array with the following structure: [ data, statusText, jqXHR ]
    var responseArgsArray = Array.prototype.slice.call(this, arguments);

});

Ini karena $.whenmenerima argumen seperti ini

$.when(ajaxRequest1, ajaxRequest2, ajaxRequest3);

Dan tidak seperti ini:

$.when([ajaxRequest1, ajaxRequest2, ajaxRequest3]);
Cory Danielson
sumber
Respon yang bagus Ada banyak lib yang menjanjikan sekarang, kebanyakan dari mereka memiliki allmetode atau yang serupa, tapi saya suka konsep peta. Bagus.
Greg
2
Ya saya hampir selalu menggunakan $. Ketika dengan menerapkan berbagai permintaan ke atasnya, dan tidak menemukan itu dalam solusi di sini, jadi tambahkan saja untuk memiliki contoh yang baik di sini.
Cory Danielson
4

Saya suka ide hvgotcodes. Saran saya adalah menambahkan incrementer generik yang membandingkan jumlah lengkap dengan jumlah yang dibutuhkan dan kemudian menjalankan panggilan balik akhir. Ini dapat dibangun ke dalam callback akhir.

var sync = {
 callbacksToComplete = 3,
 callbacksCompleted = 0,
 addCallbackInstance = function(){
  this.callbacksCompleted++;
  if(callbacksCompleted == callbacksToComplete) {
   doFinalCallBack();
  }
 }
};

[Diedit untuk mencerminkan pembaruan nama.]

Adolph Trudeau
sumber
^ Setuju, membantu mendapatkan pemahaman yang lebih baik tentang apa yang sebenarnya saya butuhkan.
MisterIsaak
3

EDIT - mungkin opsi terbaik adalah membuat titik akhir layanan yang melakukan semua yang diminta oleh ketiga permintaan. Dengan begitu Anda hanya perlu melakukan satu permintaan, dan semua data adalah di mana Anda membutuhkannya sebagai respons. Jika Anda menemukan 3 permintaan yang sama berulang kali, Anda mungkin ingin menempuh rute ini. Seringkali merupakan keputusan desain yang baik untuk mengatur layanan fasad pada server yang menggabungkan tindakan server kecil yang biasa digunakan bersama-sama. Hanya sebuah ide.


salah satu cara untuk melakukannya adalah dengan membuat objek 'sinkronisasi' di handler klik Anda sebelum panggilan ajax. Sesuatu seperti

var sync = {
   count: 0
}

Sinkronisasi akan terikat pada ruang lingkup panggilan sukses secara otomatis (penutupan). Di penangan sukses, Anda menambah hitungan, dan jika itu 3 Anda dapat memanggil fungsi lainnya.

Atau, Anda bisa melakukan sesuatu seperti

var sync = {
   success1Complete: false,
   ...
   success3Complete: false,
}

ketika setiap keberhasilan dieksekusi, itu akan mengubah nilai dalam sinkronisasi menjadi true. Anda harus memeriksa sinkronisasi untuk memastikan ketiganya benar sebelum melanjutkan.

Catat kasus di mana salah satu xhrs Anda tidak mengembalikan kesuksesan - Anda harus memperhitungkannya.

Namun pilihan lain adalah selalu memanggil fungsi terakhir dalam penangan sukses Anda, dan minta akses opsi sinkronisasi untuk menentukan apakah benar-benar melakukan sesuatu. Anda perlu memastikan bahwa sinkronisasi berada dalam lingkup fungsi itu.

hvgotcodes
sumber
Saya lebih menyukai saran pertama Anda karena beberapa alasan. Setiap permintaan memiliki tujuan yang berbeda, jadi saya ingin memisahkannya karena alasan itu. Juga permintaan sebenarnya dalam suatu fungsi karena saya menggunakannya di tempat lain, jadi saya mencoba untuk menjaga desain modular jika memungkinkan. Terima kasih atas bantuan Anda!
MisterIsaak
@ Jisaak, ya menempatkan metode di server untuk menangani ini mungkin tidak masuk akal untuk Anda Tetapi dapat diterima untuk mengatur fasad di server. Anda berbicara tentang modularitas, menciptakan satu metode yang menangani ketiga hal tersebut adalah modular.
hvgotcodes
0

Saya mendapat beberapa petunjuk bagus dari jawaban di halaman ini. Saya mengadaptasinya sedikit untuk saya gunakan dan berpikir saya bisa membagikannya.

// lets say we have 2 ajax functions that needs to be "synchronized". 
// In other words, we want to know when both are completed.
function foo1(callback) {
    $.ajax({
        url: '/echo/html/',
        success: function(data) {
            alert('foo1');
            callback();               
        }
    });
}

function foo2(callback) {
    $.ajax({
        url: '/echo/html/',
        success: function(data) {
            alert('foo2');
            callback();
        }
    });
}

// here is my simplified solution
ajaxSynchronizer = function() {
    var funcs = [];
    var funcsCompleted = 0;
    var callback;

    this.add = function(f) {
        funcs.push(f);
    }

    this.synchronizer = function() {
        funcsCompleted++;
        if (funcsCompleted == funcs.length) {
            callback.call(this);
        }
    }

    this.callWhenFinished = function(cb) {
        callback = cb;
        for (var i = 0; i < funcs.length; i++) {
            funcs[i].call(this, this.synchronizer);
        }
    }
}

// this is the function that is called when both ajax calls are completed.
afterFunction = function() {
    alert('All done!');
}

// this is how you set it up
var synchronizer = new ajaxSynchronizer();
synchronizer.add(foo1);
synchronizer.add(foo2);
synchronizer.callWhenFinished(afterFunction);

Ada beberapa batasan di sini, tetapi untuk kasus saya itu ok. Saya juga menemukan bahwa untuk hal-hal yang lebih maju ada juga plugin AOP (untuk jQuery) yang mungkin berguna: http://code.google.com/p/jquery-aop/

PålOliver
sumber
0

Saya menemukan masalah ini hari ini, dan ini adalah upaya naif saya sebelum menonton jawaban yang diterima.

<script>
    function main() {
        var a, b, c
        var one = function() {
            if ( a != undefined  && b != undefined && c != undefined ) {
                alert("Ok")
            } else {
                alert( "¬¬ ")
            }
        }

        fakeAjaxCall( function() {
            a = "two"
            one()
        } )
        fakeAjaxCall( function() {
            b = "three"
            one()
        } )
        fakeAjaxCall( function() {
            c = "four"
            one()
        } )
    }
    function fakeAjaxCall( a ) {
        a()
    }
    main()
</script>
OscarRyz
sumber
0

Ini bukan jquery (dan tampaknya jquery memiliki solusi yang bisa diterapkan) tetapi hanya sebagai pilihan lain ....

Saya memiliki masalah serupa yang banyak bekerja dengan layanan web SharePoint - Anda sering perlu menarik data dari berbagai sumber untuk menghasilkan input untuk satu proses.

Untuk mengatasinya saya menanamkan fungsionalitas semacam ini ke perpustakaan abstraksi AJAX saya. Anda dapat dengan mudah mendefinisikan permintaan yang akan memicu satu set penangan saat selesai. Namun setiap permintaan dapat didefinisikan dengan beberapa panggilan http. Berikut komponennya (dan dokumentasi terperinci):

DPAJAX di DepressedPress.com

Contoh sederhana ini membuat satu permintaan dengan tiga panggilan dan kemudian meneruskan informasi itu, dalam urutan panggilan, ke penangan tunggal:

    // The handler function 
function AddUp(Nums) { alert(Nums[1] + Nums[2] + Nums[3]) }; 

    // Create the pool 
myPool = DP_AJAX.createPool(); 

    // Create the request 
myRequest = DP_AJAX.createRequest(AddUp); 

    // Add the calls to the request 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [5,10]); 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [4,6]); 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [7,13]); 

    // Add the request to the pool 
myPool.addRequest(myRequest); 

Perhatikan bahwa tidak seperti banyak solusi lain (termasuk, saya percaya solusi "kapan" dalam jquery) asalkan metode ini tidak memaksa threading tunggal dari panggilan yang dibuat - masing-masing masih akan berjalan secepat (atau selambat) jika lingkungan memungkinkan tetapi penangan tunggal hanya akan dipanggil ketika semua sudah selesai. Ini juga mendukung pengaturan nilai batas waktu dan coba lagi jika layanan Anda sedikit flakey.

Saya telah menemukannya sangat berguna (dan sangat sederhana untuk dipahami dari perspektif kode). Tidak ada lagi rantai, tidak ada lagi menghitung panggilan dan menyimpan output. Rencanakan itu dan lupakan itu".

Jim Davis
sumber
0

Ok, ini sudah tua tapi tolong izinkan saya berkontribusi solusi saya :)

function sync( callback ){
    syncCount--;
    if ( syncCount < 1 ) callback();
}
function allFinished(){ .............. }

window.syncCount = 2;

$.ajax({
    url: 'url',
    success: function(data) {
        sync( allFinished );
    }
});
someFunctionWithCallback( function(){ sync( allFinished ); } )

Ini juga berfungsi dengan fungsi yang memiliki panggilan balik. Anda mengatur syncCount dan Anda memanggil fungsi sync (...) di callback setiap tindakan.

sic
sumber
0

Saya menemukan cara yang lebih mudah untuk melakukannya tanpa perlu metode tambahan yang mengatur antrian.

JS

$.ajax({
  type: 'POST',
  url: 'ajax1.php',
  data:{
    id: 1,
    cb:'method1'//declaration of callback method of ajax1.php
  },
  success: function(data){
  //catching up values
  var data = JSON.parse(data);
  var cb=data[0].cb;//here whe catching up the callback 'method1'
  eval(cb+"(JSON.stringify(data));");//here we calling method1 and pass all data
  }
});


$.ajax({
  type: 'POST',
  url: 'ajax2.php',
  data:{
    id: 2,
    cb:'method2'//declaration of callback method of ajax2.php
  },
  success: function(data){
  //catching up values
  var data = JSON.parse(data);
  var cb=data[0].cb;//here whe catching up the callback 'method2'
  eval(cb+"(JSON.stringify(data));");//here we calling method2 and pass all data
  }
});


//the callback methods
function method1(data){
//here we have our data from ajax1.php
alert("method1 called with data="+data);
//doing stuff we would only do in method1
//..
}

function method2(data){
//here we have our data from ajax2.php
alert("method2 called with data="+data);
//doing stuff we would only do in method2
//..
}

PHP (ajax1.php)

<?php
    //catch up callbackmethod
    $cb=$_POST['cb'];//is 'method1'

    $json[] = array(
    "cb" => $cb,
    "value" => "ajax1"
    );      

    //encoding array in JSON format
    echo json_encode($json);
?>

PHP (ajax2.php)

<?php
    //catch up callbackmethod
    $cb=$_POST['cb'];//is 'method2'

    $json[] = array(
    "cb" => $cb,
    "value" => "ajax2"
    );      

    //encoding array in JSON format
    echo json_encode($json);
?>
burung medis
sumber
-1
async   : false,

Secara default, semua permintaan dikirim secara tidak sinkron (mis. Ini disetel ke true secara default). Jika Anda membutuhkan permintaan sinkron, atur opsi ini ke false. Permintaan dan dataType: "jsonp"permintaan lintas-domain tidak mendukung operasi sinkron. Perhatikan bahwa permintaan sinkron dapat sementara mengunci browser, menonaktifkan tindakan apa pun saat permintaan aktif. Pada jQuery 1.8 , penggunaan async: falsedengan jqXHR ( $.Deferred) sudah usang; Anda harus menggunakan opsi keberhasilan / kesalahan / panggilan balik lengkap alih-alih metode yang sesuai dari objek jqXHR seperti jqXHR.done()atau yang sudah usang jqXHR.success().

Esteves Klein
sumber
Ini bukan solusi yang memadai. Anda seharusnya tidak pernah membuat permintaan Ajax sinkron.
user128216
-2
$.ajax({type:'POST', url:'www.naver.com', dataType:'text', async:false,
    complete:function(xhr, textStatus){},
    error:function(xhr, textStatus){},
    success:function( data ){
        $.ajax({type:'POST', 
            ....
            ....
            success:function(data){
                $.ajax({type:'POST',
                    ....
                    ....
            }
        }
    });

Maaf, saya tidak bisa menjelaskan apa yang saya maksud. Saya orang Korea yang tidak bisa berbicara sepatah kata pun dalam bahasa Inggris. tapi saya pikir Anda dapat dengan mudah memahaminya.

makanan ringan
sumber
Seharusnya tidak menggunakan ajax dengan cara ini. Telepon Kembali Neraka ..!
pengkhianat