2013-07-20 10 views
31

Widziałem Chaining an arbitrary number of promises in Q; moje pytanie jest inne.Jak połączyć łańcuchową liczbę obietnic w Q, w kolejności?

Jak ustawić zmienną liczbę połączeń, z których każda zwraca się asynchronicznie, w kolejności?
Scenariusz to zbiór żądań HTTP, których liczba i typ jest określany na podstawie wyników pierwszego żądania HTTP.

Chciałbym to zrobić po prostu.

widziałem też this answer co sugeruje coś takiego:

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); 
} 

... ale wydaje się niewygodne do pętli poprzez itemsToProcess w ten sposób. Lub, aby zdefiniować nową funkcję o nazwie "pętla", która streszcza rekursję. Jaki jest lepszy sposób?

+0

Wydaje się, że nie jest to zmienna liczba obietnic, ale liczba ustawiona w zależności od długości tablicy. Zdarzają się przypadki, w których faktycznie masz numer zmiennej i nie jestem pewien, że dla nich działa metoda 'reduce'. – hippietrail

+0

https://github.com/kriskowal/q#sequences będzie oficjalnym numerem referencyjnym – Aides

Odpowiedz

74

Jest ładny czysty sposób do tego z [].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 iteruje po tablicy, przekazując zwróconą wartość poprzedniej iteracji. W tym przypadku zwracasz obietnice, a więc za każdym razem, gdy próbujesz nawiązać połączenie, musisz uzyskać then. Podajesz wstępną obietnicę (tak jak w przypadku q.resolve("start")), aby wszystko zacząć.

Na początku może zająć trochę czasu, aby owinąć głowę wokół tego, co się tutaj dzieje, ale jeśli poświęcisz chwilę na przemyślenie tego, to jest to łatwy wzór do użycia w dowolnym miejscu, bez konieczności instalowania jakichkolwiek maszyn.

+10

to jest f **** mind bending. ale wciąż super;) – manu

+0

słodki! i bardzo przydatny! – frequent

+0

Co stanie się w przypadku odrzucenia obietnicy? A przynajmniej, jak poradzilibyśmy w tej sprawie? – naivedeveloper

1

Lubię ten sposób lepiej:

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); 

Kluczem tutaj jest wywołanie .then() na deferred.promise z wersji łączonej tablicy workitems. Ta then zostaje uruchomiona po rozwiązaniu początkowej odroczonej obietnicy, która znajduje się w fn dla setTimeout. W bardziej realistycznym scenariuszu odroczona obietnica zostanie rozwiązana w oddzwanianiu klienta HTTP.

Początkowe q.resolve(itemsToProcess) wywołuje rzeczy, przekazując elementy pracy do pierwszego wywołania pracy fn.

Dodałem to w nadziei, że pomoże innym.

+1

Prawdopodobnie bardziej normalne jest budowanie łańcucha '.then()' w pętli, jak w pierwszym przykładzie, zamiast przechodzenia rekursywnie. Niezależnie od wybranej metody może być ważne, aby nie zniszczyć oryginalnej tablicy. Jeśli tak, to możesz otrzymać obietnice (i ich wywołania zwrotne), aby pracować z rosnącą wartością indeksu, która działa na oryginalnej, niesfałszowanej tablicy. –

1

Oto koncepcja maszyny stanów zdefiniowanej jako Q.

Załóżmy, że funkcja HTTP zdefiniowane, dlatego zwraca obiekt Q obietnicy

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

Można zdefiniować funkcji rekurencyjnej nextState następująco:

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); 
    }); 
} 

Gdzie function process(current, result) jest funkcją dowiedzieć się, jaki będzie następny krok zgodnie ze stanem current i result z połączenia HTTP.

Kiedy go używać, jak go używać:

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

Ma to dla mnie dużo więcej sensu niż inne rozwiązania pętli while. Łatwiej też dostosować się do innych bibliotek Promise. – Seth

1

Proponuję inne rozwiązania, które wydaje mi się łatwiejsze do zrozumienia. zrobić to samo jak podczas łączenia obietnic bezpośrednio: promise.then(doSomethingFunction).then(doAnotherThingFunction);

Jeśli stawiamy, że w pętli, otrzymamy to:

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) { 
} 

Używamy function currying użyć wielu argumentów. W naszym przykładzie functionToCall.bind(this, arg1, arg2) zwróci funkcję z jednym argumentem: functionToCall(resultFromPreviousPromise) Nie musisz używać wyniku z poprzedniej obietnicy.

Powiązane problemy