2012-04-04 20 views
63

Ho codice che sembra qualcosa di simile in javascript:Come posso attendere un set di funzioni di callback asincrone?

forloop { 
    //async call, returns an array to its callback 
} 

dopo tutto di quelle chiamate asincrone sono fatto, voglio calcolare il minimo su tutte le matrici.

Come posso aspettarli tutti?

mia unica idea in questo momento è quella di avere una serie di booleani chiamati a farsi, e set di fatto [i] a true nella funzione esima di callback, poi dire mentre (non tutto è stato eseguito) {}

edit: Suppongo che una possibile, ma brutta soluzione, sarebbe quella di modificare l'array fatto in ogni callback, quindi chiamare un metodo se tutti gli altri sono impostati da ogni callback, quindi l'ultimo callback da completare chiamerà il metodo continuativo.

Grazie in anticipo.

+1

In async, vuoi dire aspettare una richiesta Ajax da completare? –

+5

Nota, 'while (non tutto è fatto) {}' non funziona. Mentre sei impegnato ad aspettare, nessuno dei tuoi callback può essere eseguito. – cHao

+0

Sì. Sto aspettando una chiamata asincrona a un'API esterna da restituire in modo che attivi i metodi di callback.Sì cHao, l'ho capito, ed è per questo che sto chiedendo aiuto qui: D – codersarepeople

risposta

137

Non sei stato molto specifico con il codice, quindi farò uno scenario. Diciamo che hai 10 chiamate ajax e vuoi accumulare i risultati da quelle 10 chiamate ajax e poi, quando hanno completato tutto, vuoi fare qualcosa. Si può fare in questo modo accumulando i dati in un array e tenere traccia di quando l'ultimo ha terminato:

contatore manuale del

var ajaxCallsRemaining = 10; 
var returnedData = []; 

for (var i = 0; i < 10; i++) { 
    doAjax(whatever, function(response) { 
     // success handler from the ajax call 

     // save response 
     returnedData.push(response); 

     // see if we're done with the last ajax call 
     --ajaxCallsRemaining; 
     if (ajaxCallsRemaining <= 0) { 
      // all data is here now 
      // look through the returnedData and do whatever processing 
      // you want on it right here 
     } 
    }); 
} 

Nota: la gestione degli errori è importante qui (non mostrata perché è specifico di come stai effettuando le chiamate ajax). Dovrai pensare a come gestirai il caso quando una chiamata ajax non si concluderà mai, con un errore o rimarrà bloccata per un lungo periodo o dopo un lungo periodo di tempo.


jQuery Promises

Aggiungendo alla mia risposta nel 2014. In questi giorni, le promesse sono spesso utilizzati per risolvere questo tipo di problema in quanto jQuery di $.ajax() restituisce già una promessa e $.when() ti consente di sapere quando una gruppo di promesse sono tutti risolti e raccoglierà i risultati di ritorno per voi:

var promises = []; 
for (var i = 0; i < 10; i++) { 
    promises.push($.ajax(...)); 
} 
$.when.apply($, promises).then(function() { 
    // returned data is in arguments[0][0], arguments[1][0], ... arguments[9][0] 
    // you can process it here 
}, function() { 
    // error occurred 
}); 

ES6 standard promette

As specified in kba's answer: se si dispone di un ambiente con promesse nativi incorporati (browser moderno o node.js o utilizzando babeljs transpile o utilizzando un polyfill promessa), quindi è possibile utilizzare promesse ES6-specificati. Vedi this table per il supporto del browser. Le promesse sono supportate in quasi tutti i browser correnti, tranne IE.

Se doAjax() restituisce una promessa, allora si può fare questo:

var promises = []; 
for (var i = 0; i < 10; i++) { 
    promises.push(doAjax(...)); 
} 
Promise.all(promises).then(function() { 
    // returned data is in arguments[0], arguments[1], ... arguments[n] 
    // you can process it here 
}, function(err) { 
    // error occurred 
}); 

Se avete bisogno di fare un'operazione non promessa asincrona in uno che restituisce una promessa, è possibile "promisify" si in questo modo:

function doAjax(...) { 
    return new Promise(function(resolve, reject) { 
     someAsyncOperation(..., function(err, result) { 
      if (err) return reject(err); 
      resolve(result); 
     }); 
    }); 
} 

E, quindi utilizzare il modello di cui sopra:

var promises = []; 
for (var i = 0; i < 10; i++) { 
    promises.push(doAjax(...)); 
} 
Promise.all(promises).then(function() { 
    // returned data is in arguments[0], arguments[1], ... arguments[n] 
    // you can process it here 
}, function(err) { 
    // error occurred 
}); 

Bluebird Promises

Se si utilizza un più ricco di funzionalità biblioteca come la Bluebird promise library, quindi ha alcune funzioni aggiuntive costruiti in per rendere questo più facile:

var doAjax = Promise.promisify(someAsync); 
var someData = [...] 
Promise.map(someData, doAjax).then(function(results) { 
    // all ajax results here 
}, function(err) { 
    // some error here 
}); 
+1

La soluzione jQuery funziona abbastanza bene per me! – larrydahooster

+3

@kba - Non avrei definito esattamente questa risposta obsoleta in quanto tutte le tecniche sono ancora applicabili, in particolare se stai già utilizzando jQuery per Ajax. Ma, l'ho aggiornato in diversi modi per includere promesse native. – jfriend00

+0

questi giorni c'è una soluzione molto più pulita che non ha nemmeno bisogno di jQuery. Lo sto facendo con FetchAPI e Promises –

9

È possibile utilizzare l'oggetto Deferred di jQuery insieme al metodo when.

deferredArray = []; 
forloop { 
    deferred = new $.Deferred(); 
    ajaxCall(function() { 
     deferred.resolve(); 
    } 
    deferredArray.push(deferred); 
} 

$.when(deferredArray, function() { 
    //this code is called after all the ajax calls are done 
}); 
+7

La domanda non è stata taggata per 'jQuery', che di solito significa che l'OP non voleva una risposta jQuery. – jfriend00

+8

@ jfriend00 Non volevo reinventare la ruota quando era già stata creata in jQuery – Paul

+4

@Paul quindi piuttosto reinventare la ruota includendo 40kb di junk per fare qualcosa di semplice (posticipato) – Raynos

7

è possibile emulare così:

countDownLatch = { 
    count: 0, 
    check: function() { 
     this.count--; 
     if (this.count == 0) this.calculate(); 
    }, 
    calculate: function() {...} 
    }; 

quindi ogni chiamata asincrona d OES questo:

countDownLatch.count++; 

mentre in ogni chiamata asincrono di nuovo alla fine del metodo si aggiunge questa linea:

countDownLatch.check(); 

In altre parole, si emulare una funzionalità di count-down-fermo.

+0

Nel 99% di tutti i casi d'uso una promessa è la strada da percorrere ma mi piace questa risposta perché illustra un metodo per gestire il codice asincrono in situazioni in cui un polyfill Promise è più grande del JS che lo usa! – Sukima

1

Utilizzare una libreria di flusso di controllo come after

after.map(array, function (value, done) { 
    // do something async 
    setTimeout(function() { 
     // do something with the value 
     done(null, value * 2) 
    }, 10) 
}, function (err, mappedArray) { 
    // all done, continue here 
    console.log(mappedArray) 
}) 
9

Check-in a partire dal 2015: Ora abbiamo native promises a most recent browser (bordo 12, Firefox 40, Chrome 43, Safari 8, Opera 32 e browser di Android 4.4.4 e iOS Safari 8.4, ma non Internet Explorer, Opera Mini e versioni precedenti di Android).

Se vogliamo realizzare 10 azioni asincroni e ricevere una notifica quando hanno tutto finito, siamo in grado di utilizzare il nativo Promise.all, senza librerie esterne:

function asyncAction(i) { 
    return new Promise(function(resolve, reject) { 
     var result = calculateResult(); 
     if (result.hasError()) { 
      return reject(result.error); 
     } 
     return resolve(result); 
    }); 
} 

var promises = []; 
for (var i=0; i < 10; i++) { 
    promises.push(asyncAction(i)); 
} 

Promise.all(promises).then(function AcceptHandler(results) { 
    handleResults(results), 
}, function ErrorHandler(error) { 
    handleError(error); 
}); 
+1

'Promises.all()' dovrebbe essere 'Promise.all()'. – jfriend00

+1

La risposta deve anche fare riferimento a [quali browser] (http://caniuse.com/#feat=promises) è possibile utilizzare 'Promise.all()' in cui non sono presenti versioni attuali di IE. – jfriend00

2

Questo è il modo più accurato, a mio parere .

Promise.all

FetchAPI

(per qualche motivo Array.map non funziona all'interno di funzioni Then per me. Ma è possibile utilizzare un e .concat .forEach []() o qualcosa di simile)

Promise.all([ 
    fetch('/user/4'), 
    fetch('/user/5'), 
    fetch('/user/6'), 
    fetch('/user/7'), 
    fetch('/user/8') 
]).then(responses => { 
    return responses.map(response => {response.json()}) 
}).then((values) => { 
    console.log(values); 
}) 
+0

Penso che questo debba essere restituito responses.map (response => {return response.json();}) ', o' return responses.map (response => response.json()) '. –

Problemi correlati