2013-07-20 8 views
31

Ho visto Chaining an arbitrary number of promises in Q; la mia domanda è diversa.Come concatenare un numero variabile di promesse in Q, in ordine?

Come è possibile effettuare 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 visto anche this answer che suggerisce qualcosa di simile:

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 difficile da collegare attraverso l'itemsToProcess in quel modo. O per definire una nuova funzione chiamata "loop" che astrae la ricorsione. Qual è un modo migliore?

+0

Questo sembra non essere un numero variabile di promesse, ma un numero impostato in base alla lunghezza dell'array. Ci sono casi in cui hai effettivamente un numero variabile e non sono sicuro che il metodo 'reduce' funzioni per quelli. – hippietrail

+0

https://github.com/kriskowal/q#sequences sarebbe il riferimento ufficiale – Aides

risposta

74

C'è un modo pulito bello a questo 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 scorre la matrice, passando nel valore restituito della iterazione precedente. In questo caso stai restituendo le promesse, e così ogni volta che stai incatenando uno then. Fornisci una promessa iniziale (come hai fatto con q.resolve("start")) per dare il via.

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

+10

questo è f **** mente flettente. ma comunque fantastico;) – manu

+0

dolce! e molto utile! – frequent

+0

Cosa succede se una promessa viene respinta? O, almeno, come gestiremmo quel caso? – naivedeveloper

1

mi piace questo modo migliore:

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 è quello di chiamare .then() sul deferred.promise con una versione impiombato della matrice di elementi di lavoro. Questo then viene eseguito dopo la risoluzione della promessa differita iniziale, che si trova in fn per il setTimeout. In uno scenario più realistico, la promessa differita verrà risolta nel callback del client http.

L'iniziale q.resolve(itemsToProcess) dà il via passando gli elementi di lavoro alla prima chiamata del lavoro fn.

Ho aggiunto questo nella speranza di aiutare gli altri.

+1

Probabilmente è più normale costruire una catena '.then()' in un ciclo come per il primo esempio piuttosto che ricorsivo. Indipendentemente dal modo in cui scegli, potrebbe essere importante non distruggere l'array originale. Se è così, allora è possibile ottenere le promesse (e le loro callback) per lavorare con un valore di indice crescente che opera sull'array originale unsullied. –

1

Ecco un concetto di macchina a stati definita con Q.

Supponiamo di avere la funzione HTTP definito, quindi restituisce un oggetto Q promessa:

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

si può 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 function process(current, result) è una funzione di scoprire quale sarà il prossimo passo in base allo stato current e allo result dalla chiamata HTTP.

Quando lo si utilizza, usarlo come:

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

Per me è molto più sensato rispetto alle altre soluzioni di ciclo continuo. È anche più facile adattarsi ad altre librerie Promise. – Seth

1

Propongo un'altra soluzione, che mi sembra più facile da capire. Fate la stessa come quando il concatenamento promesse direttamente: promise.then(doSomethingFunction).then(doAnotherThingFunction);

Se mettiamo che in un ciclo, 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) { 
} 

Usiamo function currying di 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 promessa precedente.