callback jQuery para múltiples llamadas ajax

Quiero hacer tres llamadas ajax en un evento de clic. Cada llamada ajax realiza una operación distinta y devuelve los datos que se necesitan para una callback final. Las llamadas en sí mismas no dependen entre sí, todas pueden ir al mismo tiempo, sin embargo, me gustaría tener una callback final cuando las tres estén completas.

$('#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*/}); }); 

Aquí hay un objeto de callback que escribí en el que puede establecer una callback única para que se inicie una vez que todo esté completo o dejar que cada uno tenga su propia callback y activarlos todos una vez que todos estén completos:

DARSE CUENTA

Desde jQuery 1.5+ puede usar el método diferido como se describe en otra respuesta:

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

Ejemplo de diferido aquí

para jQuery <1.5 funcionará lo siguiente o si necesita que sus llamadas ajax se activen en momentos desconocidos como se muestra aquí con dos botones: disparados después de hacer clic en ambos botones

[uso]

para una sola callback una vez completa: Ejemplo de trabajo

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

Cada uno tiene su propia callback cuando todo está completo: Ejemplo de trabajo

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

[El código]

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

Parece que tienes algunas respuestas a esto, sin embargo, creo que hay algo que vale la pena mencionar aquí que simplificará enormemente tu código. jQuery introdujo $.when en v1.5. Parece que:

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

No lo he visto mencionado aquí, espero que ayude.

No veo la necesidad de ningún objeto malarky yo mismo. Simple tiene una variable que es un número entero. Cuando inicie una solicitud, incremente el número. Cuando uno completa, disminuya. Cuando es cero, no hay solicitudes en curso, entonces listo.

 $('#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 } }); }); 

Vale la pena señalar que, dado que $.when espera todas las solicitudes ajax como argumentos secuenciales (no una matriz), normalmente verá $.when usa con .apply() manera:

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

Esto es porque $.when acepta argumentos como este

 $.when(ajaxRequest1, ajaxRequest2, ajaxRequest3); 

Y no así:

 $.when([ajaxRequest1, ajaxRequest2, ajaxRequest3]); 

EDITAR: quizás la mejor opción sea crear un punto final de servicio que haga todo lo que hacen las tres solicitudes. De esta manera, solo tiene que hacer una solicitud, y todos los datos están donde los necesita en la respuesta. Si descubre que está haciendo las mismas 3 solicitudes una y otra vez, es probable que desee seguir esta ruta. A menudo, es una buena decisión de diseño establecer un servicio de fachada en el servidor que agrupe las acciones de servidor más pequeñas usadas comúnmente juntas. Solo una idea.


Una forma de hacerlo sería crear un objeto ‘sincronizado’ en su controlador de clics antes de las llamadas ajax. Algo como

 var sync = { count: 0 } 

La sincronización estará vinculada al scope de las llamadas de éxito automáticamente (cierre). En el controlador de éxito, incrementa el recuento, y si es 3 puede llamar a la otra función.

Alternativamente, podrías hacer algo como

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

cuando se ejecuta cada éxito, cambiaría el valor en la sincronización a verdadero. Debería verificar la sincronización para asegurarse de que los tres sean verdaderos antes de continuar.

Tenga en cuenta el caso en el que uno de sus xhrs no devuelve el éxito; necesita dar cuenta de eso.

Otra opción más sería llamar siempre a la función final en sus manejadores de éxito y hacer que acceda a la opción de sincronización para determinar si realmente se hace algo. Sin embargo, necesitaría asegurarse de que la sincronización esté dentro del scope de esa función.

Me gusta la idea de hvgotcodes. Mi sugerencia es agregar un incremento genérico que compare el número completo con el número necesario y luego ejecute la callback final. Esto podría integrarse en la callback final.

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

[Editado para reflejar las actualizaciones de nombres]

Hice la misma pregunta hace un tiempo y obtuve un par de buenas respuestas aquí: la mejor manera de agregar una ‘callback’ después de una serie de llamadas XHR asincrónicas

Tengo algunas buenas pistas de las respuestas en esta página. Lo adapté un poco para mi uso y pensé que podría compartirlo.

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

Aquí hay algunas limitaciones, pero para mi caso estaba bien. También encontré que para cosas más avanzadas, también hay un complemento de AOP (para jQuery) que podría ser útil: http://code.google.com/p/jquery-aop/

Me encontré con este problema hoy, y este fue mi bash ingenuo antes de ver la respuesta aceptada.

  

No es jquery (y parece que jquery tiene una solución viable), pero solo como otra opción …

He tenido problemas similares para trabajar en gran medida con los servicios web de SharePoint; a menudo necesita extraer datos de múltiples fonts para generar entradas para un solo proceso.

Para resolverlo integé este tipo de funcionalidad en mi biblioteca de abstracción AJAX. Puede definir fácilmente una solicitud que activará un conjunto de controladores cuando se complete. Sin embargo, cada solicitud se puede definir con múltiples llamadas http. Aquí está el componente (y la documentación detallada):

DPAJAX en DepressedPress.com

Este sencillo ejemplo crea una solicitud con tres llamadas y luego pasa esa información, en el orden de las llamadas, a un único controlador:

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

Tenga en cuenta que a diferencia de muchas de las otras soluciones (incluida la solución “cuando” en jquery), siempre que este método no fuerce el enrutamiento único de las llamadas realizadas, cada una funcionará tan rápido (o tan lentamente) como lo permita el entorno. pero solo se llamará al controlador único cuando todos estén completos. También admite el establecimiento de valores de tiempo de espera e bashs de rebash si su servicio es un poco escaso.

Lo encontré increíblemente útil (e increíblemente simple de entender desde la perspectiva del código). No más encadenamiento, no más llamadas de conteo y producción de ahorro. Solo “configúralo y olvídalo”.

 async : false, 

Por defecto, todas las solicitudes se envían de forma asíncrona (es decir, se establece en verdadero de manera predeterminada). Si necesita solicitudes sincrónicas, establezca esta opción en false . Solicitudes entre dominios y tipo de datos dataType: "jsonp" solicitudes dataType: "jsonp" no son compatibles con la operación síncrona. Tenga en cuenta que las solicitudes sincrónicas pueden bloquear temporalmente el navegador, deshabilitando cualquier acción mientras la solicitud está activa. A partir de jQuery 1.8 , el uso de async: false con jqXHR ( $.Deferred ) está en desuso; debe usar las opciones de éxito / error / callback completa en lugar de los métodos correspondientes del objeto jqXHR.done() como jqXHR.done() o el jqXHR.success() desuso.

 $.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', .... .... } } }); 

Lo siento pero no puedo explicar lo que gané porque soy coreano y no puedo hablar ni una palabra en inglés. pero creo que puedes entenderlo fácilmente