2011-12-23 29 views
32

Necesito hacer una serie de solicitudes N ajax sin bloquear el navegador, y quiero usar el objeto diferido jquery para lograr esto.Cómo encadenar llamadas ajax usando jquery

Aquí hay un ejemplo simplificado con tres solicitudes, pero mi programa puede necesitar hacer cola más de 100 (tenga en cuenta que este no es el caso de uso exacto, el código real necesita asegurar el éxito del paso (N-1) antes de ejecutar el paso siguiente):

$(document).ready(function(){ 

    var deferred = $.Deferred(); 

    var countries = ["US", "CA", "MX"]; 

    $.each(countries, function(index, country){ 

     deferred.pipe(getData(country)); 

    }); 

}); 

function getData(country){ 

    var data = { 
     "country": country 
    }; 


    console.log("Making request for [" + country + "]"); 

    return $.ajax({ 
     type: "POST", 
     url: "ajax.jsp", 
     data: data, 
     dataType: "JSON", 
     success: function(){ 
      console.log("Successful request for [" + country + "]"); 
     } 
    }); 

} 

Esto es lo que se escribe en la consola (todas las solicitudes se realizan en paralelo y el tiempo de respuesta es directamente proporcional al tamaño de los datos de cada país como se esperaba:

Making request for [US] 
Making request for [CA] 
Making request for [MX] 
Successful request for [MX] 
Successful request for [CA] 
Successful request for [US] 

¿Cómo puedo obtener el objeto diferido para poner esto en cola para mí? Intenté cambiar hecho a tubo pero obtuve el mismo resultado.

Aquí es el resultado deseado:

Making request for [US] 
Successful request for [US] 
Making request for [CA] 
Successful request for [CA] 
Making request for [MX] 
Successful request for [MX] 

Editar:

I apreciar la sugerencia de utilizar una matriz para almacenar parámetros de la petición, pero el objeto jquery diferido tiene la capacidad de hacer cola solicitudes y Realmente quiero aprender a usar esta característica en todo su potencial.

Esto es efectivamente lo que estoy tratando de hacer:

when(request[0]).pipe(request[1]).pipe(request[2])... pipe(request[N]); 

Sin embargo, quiero asignar las solicitudes en el tubo de un paso a la vez con el fin de utilizar eficazmente el recorrido de cada una:

deferred.pipe(request[0]); 
deferred.pipe(request[1]); 
deferred.pipe(request[2]); 

Respuesta

29

Con un objeto personalizado

function DeferredAjax(opts) { 
    this.options=opts; 
    this.deferred=$.Deferred(); 
    this.country=opts.country; 
} 
DeferredAjax.prototype.invoke=function() { 
    var self=this, data={country:self.country}; 
    console.log("Making request for [" + self.country + "]"); 

    return $.ajax({ 
     type: "GET", 
     url: "wait.php", 
     data: data, 
     dataType: "JSON", 
     success: function(){ 
      console.log("Successful request for [" + self.country + "]"); 
      self.deferred.resolve(); 
     } 
    }); 
}; 
DeferredAjax.prototype.promise=function() { 
    return this.deferred.promise(); 
}; 


var countries = ["US", "CA", "MX"], startingpoint = $.Deferred(); 
startingpoint.resolve(); 

$.each(countries, function(ix, country) { 
    var da = new DeferredAjax({ 
     country: country 
    }); 
    $.when(startingpoint).then(function() { 
     da.invoke(); 
    }); 
    startingpoint= da; 
}); 

violín http://jsfiddle.net/7kuX9/1/

Para ser un poco más claro, las últimas líneas se podría escribir

c1=new DeferredAjax({country:"US"}); 
c2=new DeferredAjax({country:"CA"}); 
c3=new DeferredAjax({country:"MX"}); 

$.when(c1).then(function() {c2.invoke();}); 
$.when(c2).then(function() {c3.invoke();}); 

Con tubos

function fireRequest(country) { 
     return $.ajax({ 
      type: "GET", 
      url: "wait.php", 
      data: {country:country}, 
      dataType: "JSON", 
      success: function(){ 
       console.log("Successful request for [" + country + "]"); 
      } 
     }); 
} 

var countries=["US","CA","MX"], startingpoint=$.Deferred(); 
startingpoint.resolve(); 

$.each(countries,function(ix,country) { 
    startingpoint=startingpoint.pipe(function() { 
     console.log("Making request for [" + country + "]"); 
     return fireRequest(country); 
    }); 
}); 

http://jsfiddle.net/k8aUj/1/

Editar: Un violín salida del registro en la ventana de resultados http://jsfiddle.net/k8aUj/3/

Cada llamada tubería devuelve una nueva promesa, que es a su vez se utiliza para la siguiente tubería. Tenga en cuenta que solo proporcioné la función sccess, se debe proporcionar una función similar para las fallas.

En cada solución, las llamadas Ajax se retrasan hasta que sea necesario envolviéndolas en una función y se crea una nueva promesa para cada elemento de la lista para construir la cadena.

Creo que el objeto personalizado proporciona una manera más fácil de manipular la cadena, pero las tuberías podrían adaptarse mejor a sus gustos.

Nota: a partir de jQuery 1.8, deferred.pipe() está en desuso, deferred.then reemplaza.

+0

Esta respuesta definitivamente funciona, estoy tratando de digerir todo, ya que es bastante complejo.¡Gracias! – Graham

+0

Creo que estoy viendo esto ahora. La diferencia principal entre mi código original y el tuyo parece ser que estás creando objetos Diferidos para cada solicitud, donde el mío estaba tratando de usar un solo Diferido. ¿Estoy en lo correcto? – Graham

+1

Algunas preguntas específicas para usted: (1) ¿por qué devuelve explícitamente una promesa cuando ya está devolviendo la promesa de la llamada ajax? (2) ¿por qué se asigna "esto" a "sí mismo"? (3) ¿por qué no eligió usar pipe() cuando esa es la función de cola jquery nativa? (4) Cuando estamos creando objetos diferidos para cada solicitud, ¿cuál es el requisito de memoria cuando empiezo a enviar cientos de solicitudes a la "cola"? ¿Qué tan ligero es un objeto diferido? – Graham

4

No estoy seguro del motivo por el que desea hacer esto, pero conserve una lista de todas las URL que necesita solicitar y no solicite la siguiente hasta que se llame a su función success. I.E., success realizará llamadas adicionales de manera condicional al deferred.

+0

No puedo copiar el código exacto aquí por razones de confidencialidad del cliente, pero tengo una muy buena razón para encadenar estas llamadas secuencialmente. ¿Su solución no requeriría que toda la matriz pasara a la función getData? – Graham

+0

Más o menos, o disponible de otro modo en algún lugar arriba de la función 'getData' en la cadena de alcance. Por ejemplo, tenga sus 2 bloques de código originales (en su pregunta original) en un cierre, combinados con el conjunto como una 3ra sección. También necesitaría hacer un seguimiento de las solicitudes que ya se realizaron, pero podría manejar esto simplemente haciendo estallar los elementos cuando los solicite (tratándolo como una pila). – ziesemer

+1

Es tangencial, pero ¿podría ampliar también por qué no puede ver por qué querría hacer cola en las solicitudes de AJAX? Hay dos casos de uso muy buenos en los que puedo pensar: 1. limitar cuántas solicitudes simultáneas se envían a un servidor, y 2. posibles dependencias entre las solicitudes. – Graham

2

Actualización: deferred.pipe está en desuso

Se trata de una gran cantidad de código de algo que ya está documentado en la API de jQuery. ver http://api.jquery.com/deferred.pipe/

Puedes seguir puliéndolos hasta que se hagan 100.

O, escribí algo para hacer N llamadas, y resolver una sola función con los datos de todas las llamadas que se han realizado. Nota: devuelve los datos, no el super objeto XHR. https://gist.github.com/1219564

+0

En el momento en que originalmente publiqué esta pregunta, había muy poca documentación disponible. – Graham

4

Sé que llego tarde a esto, pero creo que su código original es bastante bueno pero tiene dos (tal vez tres) problemas.

Se ha llamado inmediatamente a su getData(country) por la forma en que ha codificado el parámetro de su tubería. La forma en que la tiene, getData() se está ejecutando inmediatamente y el resultado (la promesa de Ajax, pero la solicitud http comienza inmediatamente) se pasa como el parámetro a pipe(). Por lo tanto, en lugar de pasar una función de devolución de llamada, está pasando un objeto, lo que provoca que el nuevo diferido de la tubería se resuelva inmediatamente.

creo que tiene que ser

deferred.pipe(function() { return getData(country); }); 

Ahora es una función de devolución de llamada que se llamará cuando el padre del tubo diferido se ha resuelto. Codificarlo de esta manera generará el segundo problema. Ninguno de los getData() s se ejecutará hasta que se resuelva el maestro diferido.

El tercer problema potencial podría ser que debido a que todas sus tuberías estarían conectadas al maestro diferido, realmente no tiene una cadena y me pregunto si podría ejecutarlas todas al mismo tiempo de todos modos. Los documentos dicen que las devoluciones de llamada se ejecutan en orden, pero dado que la devolución de llamada devuelve una promesa y se ejecuta de forma asíncrona, es posible que todavía se ejecuten en paralelo.

Por lo tanto, creo que necesita algo como esto

var countries = ["US", "CA", "MX"]; 
var deferred = $.Deferred(); 
var promise = deferred.promise(); 

$.each(countries, function(index, country) { 
    promise = promise.pipe(function() { return getData(country); }); 
}); 

deferred.resolve(); 
+0

Interesante, tendré que probar esto. No tuve el requisito de capturar la secuencia en los valores de retorno, pero siempre es bueno profundizar en jquery. – Graham

+0

No estoy seguro de a qué se refiere con la secuencia _capture en los valores devueltos_. ¿Podrías elaborar? –

+0

este es el que más me gusta porque luego puedo resolverlo en un momento posterior dado que 'diferido' se inmoviliza como un objeto diferido pero' promesa' es solo un objeto Promesa. por ejemplo, podría agregar algo a la cadena de promesa más adelante y solo quiero resolverlo cuando todo ese trabajo esté terminado. Pero la respuesta aceptada sigue siendo aceptable si está de acuerdo con las devoluciones de llamada que se ejecutan a medida que se agregan. todavía está en orden para que se agreguen. – gillyspy

5

Nota: A partir de jQuery 1.8 se puede utilizar en lugar de .then.pipe. La función .then ahora devuelve una nueva promesa y .pipe está en desuso, ya que ya no es necesaria. Consulte promises spec para obtener más información acerca de las promesas y q.js para obtener una biblioteca más limpia de las promesas de javascript sin una dependencia de jquery.

countries.reduce(function(l, r){ 
    return l.then(function(){return getData(r)}); 
}, $.Deferred().resolve()); 

y si te gusta usar q.JS:

//create a closure for each call 
function getCountry(c){return function(){return getData(c)};} 
//fire the closures one by one 
//note: in Q, when(p1,f1) is the static version of p1.then(f1) 
countries.map(getCountry).reduce(Q.when, Q()); 

Respuesta original:

Sin embargo, otra tubería; no para los débiles de corazón, pero un poco más compacto:

countries.reduce(function(l, r){ 
    return l.pipe(function(){return getData(r)}); 
}, $.Deferred().resolve()); 

Reduce documentation es probablemente el mejor lugar para empezar a entender cómo funciona el código de seguridad. Básicamente, toma dos argumentos, una devolución de llamada y un valor inicial.

La devolución de llamada se aplica iterativamente sobre todos los elementos de la matriz, donde su primer argumento se alimenta con el resultado de la iteración anterior, y el segundo argumento es el elemento actual. El truco aquí es que el getData() devuelve un jquery deferred promise, y el conducto se asegura de que antes de que se llame a getData en el elemento actual, se complete el getData del elemento anterior.

El segundo argumento $.Deferred().resolve() es una expresión idiomática de un valor diferido resuelto. Se alimenta a la primera iteración de la ejecución de devolución de llamada, y se asegura de que se llama inmediatamente a getData en el primer elemento.

+0

Si pudiera comentar, explicar y formatear esta respuesta en más de tres líneas, creo que podría ser una muy buena respuesta. Para muchos si no para todos nosotros, este tipo de cosas es muy difícil de asimilar, incluso si tenemos la corazonada de 'reducir' podría ser utilizado. – hippietrail

+0

Gran solución FP, me gusta –

2

Tuve éxito con las colas jQuery.

$(function(){ 
    $.each(countries, function(i,country){ 
     $('body').queue(function() { 
     getData(country); 
     }); 
    }); 
}); 

var getData = function(country){ 
    $.ajax({ 
    url : 'ajax.jsp', 
    data : { country : country }, 
    type : 'post', 
    success : function() {       
     // Que up next ajax call 
     $('body').dequeue(); 
    }, 
    error : function(){ 
     $('body').clearQueue(); 
    } 
    }); 
}; 
+0

simple eficiente, funcionó como un encanto gracias – Sherlock

Cuestiones relacionadas