2009-06-29 13 views
62

Desidero aggiornare una pagina in base ai risultati di più richieste ajax/json. Utilizzando jQuery, posso "catena" i callback, come questo esempio molto semplice ridotta:Richieste Ajax asincrone parallele utilizzando jQuery

$.getJSON("/values/1", function(data) { 
    // data = {value: 1} 
    var value_1 = data.value; 

    $.getJSON("/values/2", function(data) { 
    // data = {value: 42} 
    var value_2 = data.value; 

    var sum = value_1 + value_2; 

    $('#mynode').html(sum); 
    }); 

}); 

Tuttavia, questo si traduce in richieste compiuti in serie. Preferirei piuttosto un modo per rendere le richieste in parallelo ed eseguire l'aggiornamento della pagina dopo che tutti sono stati completati. C'è un modo per fare questo?

risposta

92

Provare questa soluzione, che può supportare qualsiasi numero specifico di query parallele:

var done = 4; // number of total requests 
var sum = 0; 

/* Normal loops don't create a new scope */ 
$([1,2,3,4,5]).each(function() { 
    var number = this; 
    $.getJSON("/values/" + number, function(data) { 
    sum += data.value; 
    done -= 1; 
    if(done == 0) $("#mynode").html(sum); 
    }); 
}); 
+25

Se non sbaglio, tu sei l'autore di "jQuery In Action"? – karim79

+9

Sì! Stiamo lavorando alla seconda edizione mentre parliamo :) –

+1

Great book! Il tuo nome mi ha fatto suonare degli allarmi nella testa! – karim79

1

Se il risultato di una richiesta dipende dall'altra, non è possibile renderle parallele.

+1

Uno non dipende dall'altra, ma il risultato finale dipende ogni fase di completamento. – Paul

+1

Poiché si tratta di una semplice operazione matematica tra i dati restituiti, allora sì, è possibile utilizzare una variabile fuori ambito per tenere traccia dei dati aggiunti. Ma nella maggior parte dei casi questa non è una soluzione valida per le richieste parallele che dipendono dai rispettivi dati. –

5

si potrebbe fare qualcosa di simile

var allData = [] 
$.getJSON("/values/1", function(data) { 
    allData.push(data); 
    if(data.length == 2){ 
     processData(allData) // where process data processes all the data 
    } 
}); 

$.getJSON("/values/2", function(data) { 
    allData.push(data); 
    if(data.length == 2){ 
     processData(allData) // where process data processes all the data 
    } 
}); 

var processData = function(data){ 
    var sum = data[0] + data[1] 
    $('#mynode').html(sum); 
} 
8

Ecco il mio tentativo di affrontare direttamente la tua domanda

Fondamentalmente, ti basta creare stack di chiamate AJAX, eseguirli tutti e una funzione fornita viene chiamata al completamento di tutti gli eventi - l'argomento fornito è un arr uno dei risultati di tutte le richieste Ajax fornite.

Chiaramente questo è il codice precoce - è possibile ottenere più elaborato con questo in termini di flessibilità.

<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script> 
<script type="text/javascript"> 

var ParallelAjaxExecuter = function(onComplete) 
{ 
    this.requests = []; 
    this.results = []; 
    this.onComplete = onComplete; 
} 

ParallelAjaxExecuter.prototype.addRequest = function(method, url, data, format) 
{ 
    this.requests.push({ 
     "method" : method 
    , "url"  : url 
    , "data"  : data 
    , "format" : format 
    , "completed" : false 
    }) 
} 

ParallelAjaxExecuter.prototype.dispatchAll = function() 
{ 
    var self = this; 
    $.each(self.requests, function(i, request) 
    { 
    request.method(request.url, request.data, function(r) 
    { 
     return function(data) 
     { 
     console.log 
     r.completed = true; 
     self.results.push(data); 
     self.checkAndComplete(); 
     } 
    }(request)) 
    }) 
} 

ParallelAjaxExecuter.prototype.allRequestsCompleted = function() 
{ 
    var i = 0; 
    while (request = this.requests[i++]) 
    { 
    if (request.completed === false) 
    { 
     return false; 
    } 
    } 
    return true; 
}, 

ParallelAjaxExecuter.prototype.checkAndComplete = function() 
{ 
    if (this.allRequestsCompleted()) 
    { 
    this.onComplete(this.results); 
    } 
} 

var pe = new ParallelAjaxExecuter(function(results) 
{ 
    alert(eval(results.join('+'))); 
}); 

pe.addRequest($.get, 'test.php', {n:1}, 'text'); 
pe.addRequest($.get, 'test.php', {n:2}, 'text'); 
pe.addRequest($.get, 'test.php', {n:3}, 'text'); 
pe.addRequest($.get, 'test.php', {n:4}, 'text'); 

pe.dispatchAll(); 

</script> 

ecco test.php Aggiornamento

<?php 

echo pow($_GET['n'], 2); 

?> 
+15

OO andato troppo lontano. – jmah

8

: Per la risposta data da Yair Leviel, questa risposta è obsoleto. Utilizza una libreria di promessa, come jQuery.when() o Q.js.


Ho creato una soluzione generica come estensione jQuery. Potrei usare un po 'di messa a punto per renderlo più generale, ma è adatto alle mie esigenze. Il vantaggio di questa tecnica rispetto agli altri in questa pubblicazione al momento della stesura di questo articolo è che è possibile utilizzare qualsiasi tipo di elaborazione asincrona con callback da .

Nota: mi piacerebbe utilizzare le estensioni Rx per JavaScript, invece di questo se ho pensato che il mio cliente sarebbe stato bene con l'assunzione di una dipendenza da UPDATE yet-another-di terze parti-libreria :)

// jQuery extension for running multiple async methods in parallel 
// and getting a callback with all results when all of them have completed. 
// 
// Each worker is a function that takes a callback as its only argument, and 
// fires up an async process that calls this callback with its result. 
// 
// Example: 
//  $.parallel(
//   function (callback) { $.get("form.htm", {}, callback, "html"); }, 
//   function (callback) { $.post("data.aspx", {}, callback, "json"); }, 
//   function (formHtml, dataJson) { 
//    // Handle success; each argument to this function is 
//    // the result of correlating ajax call above. 
//   } 
//  ); 

(function ($) { 

    $.parallel = function (anyNumberOfWorkers, allDoneCallback) { 

    var workers = []; 
    var workersCompleteCallback = null; 

    // To support any number of workers, use "arguments" variable to 
    // access function arguments rather than the names above. 
    var lastArgIndex = arguments.length - 1; 
    $.each(arguments, function (index) { 
     if (index == lastArgIndex) { 
      workersCompleteCallback = this; 
     } else { 
      workers.push({ fn: this, done: false, result: null }); 
     } 
    }); 

    // Short circuit this edge case 
    if (workers.length == 0) { 
     workersCompleteCallback(); 
     return; 
    } 

    // Fire off each worker process, asking it to report back to onWorkerDone. 
    $.each(workers, function (workerIndex) { 
     var worker = this; 
     var callback = function() { onWorkerDone(worker, arguments); }; 
     worker.fn(callback); 
    }); 

    // Store results and update status as each item completes. 
    // The [0] on workerResultS below assumes the client only needs the first parameter 
    // passed into the return callback. This simplifies the handling in allDoneCallback, 
    // but may need to be removed if you need access to all parameters of the result. 
    // For example, $.post calls back with success(data, textStatus, XMLHttpRequest). If 
    // you need textStatus or XMLHttpRequest then pull off the [0] below. 
    function onWorkerDone(worker, workerResult) { 
     worker.done = true; 
     worker.result = workerResult[0]; // this is the [0] ref'd above. 
     var allResults = []; 
     for (var i = 0; i < workers.length; i++) { 
      if (!workers[i].done) return; 
      else allResults.push(workers[i].result); 
     } 
     workersCompleteCallback.apply(this, allResults); 
    } 
}; 

})(jQuery); 
+2

L'ho usato e funziona benissimo! ... ma ho un miglioramento: se modifichi l'invocazione finale del callback per utilizzare "apply", ottieni invece argomenti separati alla tua callback invece di un singolo elenco di argomenti: cioè workerCompleteCallback.applicare (questo, allResults); –

+1

Grazie, Nick - aggiornato secondo il tuo suggerimento. – pettys

7

E altri due anni dopo, questo sembra assurdo perché la risposta accettata è cambiata in qualcosa di molto meglio! (Anche se non è ancora buono come la risposta di Yair Leviel usando jQuery's when)

18 mesi dopo, ho appena colpito qualcosa di simile. Ho un pulsante di aggiornamento, e voglio il vecchio contenuto a fadeOut e poi il nuovo contenuto a fadeIn. Ma ho anche bisogno di get il nuovo contenuto. fadeOut e get sono asincroni, ma sarebbe inutile eseguirli in serie.

Quello che faccio è in realtà la stessa della risposta accettata, tranne che sotto forma di funzione riutilizzabile. La sua virtù principale è che è molto più breve rispetto agli altri suggerimenti qui.

var parallel = function(actions, finished) { 

    finishedCount = 0; 
    var results = []; 

    $.each(actions, function(i, action) { 

    action(function(result) { 

     results[i] = result; 
     finishedCount++; 

     if (finishedCount == actions.length) { 
     finished(results); 
     } 
    }); 
    }); 
}; 

Si passa a una serie di funzioni da eseguire in parallelo. Ogni funzione dovrebbe accettare un'altra funzione a cui passa il suo risultato (se presente). parallel fornirà quella funzione.

Si passa anche a una funzione da chiamare quando tutte le operazioni sono state completate. Questa riceverà un array con tutti i risultati in Quindi il mio esempio era:.

refreshButton.click(function() { 

    parallel([ 
     function(f) { 
     contentDiv.fadeOut(f); 
     }, 
     function(f) { 
     portlet.content(f); 
     }, 
    ], 
    function(results) { 
     contentDiv.children().remove(); 
     contentDiv.append(results[1]); 
     contentDiv.fadeIn(); 
    }); 
}); 

Così, quando il mio pulsante di aggiornamento viene cliccato, mi lancio effetto fadeOut di jQuery e anche la mia propria funzione portlet.content (che fa un asincrono get, costruisce un nuovo bit di contenuto e lo passa), quindi quando entrambi sono completi rimuovo il vecchio contenuto, aggiungo il risultato della seconda funzione (che è in results[1]) e il fadeIn il nuovo contenuto.

Come fadeOut non passa nulla alla sua funzione di completamento, results[0] presumibilmente contiene undefined, quindi lo ignoro. Ma se avessi avuto tre operazioni con risultati utili, avrebbero inserito ciascuna fessura nell'array results, nello stesso ordine in cui hai passato le funzioni.

101

jQuery $.when() e $.done() sono esattamente quello che vi serve:

$.when($.ajax("/page1.php"), $.ajax("/page2.php")) 
    .then(myFunc, myFailure); 
+0

+1 Avevo sentito da qualche parte che le promesse non si compongono bene ... A quanto pare ho bisogno di dimenticarlo! –

+0

Senza offesa, ma questa risposta non è di gran lunga superiore a quella di @ yehuda-katz? (Dato che questo funziona) – Herbert

+0

ugh, questa dovrebbe essere la risposta accettata. – Spongman

3

Ecco un'implementazione utilizzando mbostock/queue:

queue() 
    .defer(function(callback) { 
    $.post('/echo/json/', {json: JSON.stringify({value: 1}), delay: 1}, function(data) { 
     callback(null, data.value); 
    }); 
    }) 
    .defer(function(callback) { 
    $.post('/echo/json/', {json: JSON.stringify({value: 3}), delay: 2}, function(data) { 
     callback(null, data.value); 
    }); 
    }) 
    .awaitAll(function(err, results) { 
    var result = results.reduce(function(acc, value) { 
     return acc + value; 
    }, 0); 
    console.log(result); 
    }); 

il violino associati: http://jsfiddle.net/MdbW2/

6

eseguire più richieste AJAX in parallelo

Quando si lavora con le API, a volte è necessario inviare più richieste AJAX a diversi endpoint. Invece di aspettare una richiesta per completare prima di emettere il successivo, è possibile accelerare le cose con jQuery richiedendo i dati in parallelo, utilizzando la funzione di jQuery $.when():

JS

$.when($.get('1.json'), $.get('2.json')).then(function(r1, r2){ 
    console.log(r1[0].message + " " + r2[0].message); 
}); 

La funzione di callback viene eseguito quando entrambe queste richieste GET terminano correttamente. $ .when() prende le promesse restituite da due chiamate $ .get() e costruisce un nuovo oggetto promessa. Gli argomenti r1 e r2 del callback sono array, i cui primi elementi contengono le risposte del server.

3

Con la seguente estensione di jQuery (per può essere scritta come una funzione autonoma si può fare questo:

$.whenAll({ 
    val1: $.getJSON('/values/1'), 
    val2: $.getJSON('/values/2') 
}) 
    .done(function (results) { 
     var sum = results.val1.value + results.val2.value; 

     $('#mynode').html(sum); 
    }); 

L'estensione JQuery (1.x) whenAll():

$.whenAll = function (deferreds) { 
    function isPromise(fn) { 
     return fn && typeof fn.then === 'function' && 
      String($.Deferred().then) === String(fn.then); 
    } 
    var d = $.Deferred(), 
     keys = Object.keys(deferreds), 
     args = keys.map(function (k) { 
      return $.Deferred(function (d) { 
       var fn = deferreds[k]; 

       (isPromise(fn) ? fn : $.Deferred(fn)) 
        .done(d.resolve) 
        .fail(function (err) { d.reject(err, k); }) 
       ; 
      }); 
     }); 

    $.when.apply(this, args) 
     .done(function() { 
      var resObj = {}, 
       resArgs = Array.prototype.slice.call(arguments); 
      resArgs.forEach(function (v, i) { resObj[keys[i]] = v; }); 
      d.resolve(resObj); 
     }) 
     .fail(d.reject); 

    return d; 
}; 

Vedere jsbin esempio: http://jsbin.com/nuxuciwabu/edit?js,console

3

La soluzione più professionale per me sarebbe utilizzando async.js e Array.riduci in questo modo:

 async.map([1, 2, 3, 4, 5], function (number, callback) { 
      $.getJSON("/values/" + number, function (data) { 
       callback(null, data.value); 
      }); 
     }, function (err, results) { 
      $("#mynode").html(results.reduce(function(previousValue, currentValue) { 
       return previousValue + currentValue; 
      })); 
     }); 
0

Basandosi sulla risposta di Yair. È possibile definire le promesse Ajax in modo dinamico.

var start = 1; // starting value 
var len = 2; // no. of requests 

var promises = (new Array(len)).fill().map(function() { 
    return $.ajax("/values/" + i++); 
}); 

$.when.apply($, promises) 
    .then(myFunc, myFailure); 
0
say suppose you have a array of file name. 

var templateNameArray=["test.html","test2.html","test3.html"]; 

htmlTemplatesLoadStateMap={}; 
var deffereds=[]; 
    for (var i = 0; i < templateNameArray.length; i++) 
     { 
     if (!htmlTemplatesLoadStateMap[templateNameArray[i]]) 
      {   
       deferreds.push($.get("./Content/templates/" +templateNameArray[i], 

        function (response, status, xhr) { 
         if (status == "error") { } 
         else { 
           $("body").append(response); 
           } 
         }));    
htmlTemplatesLoadStateMap[templateNameArray[i]] = true; 
         } 
        } 
             $.when.all(deferreds).always(function(resultsArray) { yourfunctionTobeExecuted(yourPayload); 
           }); 

Hope this helps...!! 
Problemi correlati