Come concatenare un numero variabile di promesse in Q, in ordine?

Ho visto concatenare un numero arbitrario di promesse in Q ; la mia domanda è diversa.

Come posso creare un numero variabile di chiamate, ognuna delle quali restituisce in modo asincrono, in ordine?
Lo scenario è un insieme di richieste HTTP, il cui numero e tipo è determinato dai risultati della prima richiesta HTTP.

Mi piacerebbe farlo semplicemente

Ho anche visto questa risposta che suggerisce qualcosa del genere:

var q = require('q'), itemsToProcess = ["one", "two", "three", "four", "five"]; function getDeferredResult(prevResult) { return (function (someResult) { var deferred = q.defer(); // any async function (setTimeout for now will do, $.ajax() later) setTimeout(function () { var nextResult = (someResult || "Initial_Blank_Value ") + ".." + itemsToProcess[0]; itemsToProcess = itemsToProcess.splice(1); console.log("tick", nextResult, "Array:", itemsToProcess); deferred.resolve(nextResult); }, 600); return deferred.promise; }(prevResult)); } var chain = q.resolve("start"); for (var i = itemsToProcess.length; i > 0; i--) { chain = chain.then(getDeferredResult); } 

… ma sembra strano gironzolare tra gli oggetti in questo modo. O per definire una nuova funzione chiamata “loop” che astrae la ricorsione. Qual è un modo migliore?

C’è un modo carino per farlo con [].reduce .

 var chain = itemsToProcess.reduce(function (previous, item) { return previous.then(function (previousValue) { // do what you want with previous value // return your async operation return Q.delay(100); }) }, Q.resolve(/* set the first "previousValue" here */)); chain.then(function (lastResult) { // ... }); 

reduce iterazioni attraverso l’array, passando il valore restituito dell’iterazione precedente. In questo caso stai restituendo le promesse, e così ogni volta che stai incatenando un then . Fornisci una promise iniziale (come hai fatto con q.resolve("start") ) per dare il calcio d’inizio.

All’inizio può richiedere un po ‘di tempo per capire cosa sta succedendo qui, ma se ci prendi un momento per lavorarci sopra, è un modello facile da usare ovunque, senza dover installare alcun macchinario.

Mi piace in questo modo meglio:

 var q = require('q'), itemsToProcess = ["one", "two", "three", "four", "five"]; function getDeferredResult(a) { return (function (items) { var deferred; // end if (items.length === 0) { return q.resolve(true); } deferred = q.defer(); // any async function (setTimeout for now will do, $.ajax() later) setTimeout(function () { var a = items[0]; console.log(a); // pop one item off the array of workitems deferred.resolve(items.splice(1)); }, 600); return deferred.promise.then(getDeferredResult); }(a)); } q.resolve(itemsToProcess) .then(getDeferredResult); 

La chiave qui è chiamare .then() sul deferred.promise con una versione spliced ​​dell’array di workitems. Questo viene then eseguito dopo la risoluzione della promise differita iniziale, che si trova nel fn per il setTimeout. In uno scenario più realistico, la promise differita verrà risolta nel callback del client http.

Il q.resolve(itemsToProcess) iniziale q.resolve(itemsToProcess) via alle cose passando gli elementi di lavoro alla prima chiamata del lavoro fn.

Ho aggiunto questo nella speranza che possa aiutare gli altri.

Ecco un concetto di una macchina a stati definita con Q

Supponiamo di avere la funzione HTTP definita, quindi restituisce un object promise Q :

 var Q_http = function (url, options) { return Q.when($.ajax(url, options)); } 

È ansible definire una funzione ricorsiva nextState come segue:

 var states = [...]; // an array of states in the system. // this is a state machine to control what url to get data from // at the current state function nextState(current) { if (is_terminal_state(current)) return Q(true); return Q_http(current.url, current.data).then(function (result) { var next = process(current, result); return nextState(next); }); } 

Dove il function process(current, result) è una funzione per scoprire quale sarà il prossimo passo in base allo stato current e al result della chiamata HTTP.

Quando lo usi, usalo come:

 nextState(initial).then(function () { // all requests are successful. }, function (reason) { // for some unexpected reason the request sequence fails in the middle. }); 

Propongo altre soluzioni, che mi sembrano più facili da capire. Fai lo stesso come quando concateni le promesse direttamente: promise.then(doSomethingFunction).then(doAnotherThingFunction);

Se lo inseriamo in un loop, otteniamo questo:

 var chain = Q.when(); for(...) { chain = chain.then(functionToCall.bind(this, arg1, arg2)); }; chain.then(function() { console.log("whole chain resolved"); }); var functionToCall = function(arg1, arg2, resultFromPreviousPromise) { } 

Utilizziamo la funzione currying per utilizzare più argomenti. Nel nostro esempio functionToCall.bind(this, arg1, arg2) restituirà una funzione con un argomento: functionToCall(resultFromPreviousPromise) Non è necessario utilizzare il risultato della precedente promise.