Как связать переменное число обещаний в Q, по порядку?
Я видел цепочку произвольного числа обещаний в Q; мой вопрос другой.
Как я могу сделать переменное количество вызовов, каждый из которых возвращает асинхронно, по порядку?
Сценарий представляет собой набор HTTP-запросов, количество и тип которых определяются по результатам первого HTTP-запроса.
Я хотел бы сделать это просто.
Я также видел этот ответ, который предполагает что-то вроде этого:
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);
}
... но, кажется, неловко перебирать itemsToProcess таким образом. Или определить новую функцию с именем "loop", которая абстрагирует рекурсию. Какой способ лучше?
4 ответа
Там есть хороший, чистый способ [].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
перебирает массив, передавая возвращенное значение предыдущей итерации. В этом случае вы возвращаете обещания, и поэтому каждый раз, когда вы соединяете then
, Вы предоставляете первоначальное обещание (как вы сделали с q.resolve("start")
), чтобы начать вещи.
Поначалу может потребоваться некоторое время, чтобы обдумать, что здесь происходит, но если вы потратите немного времени на его проработку, то его легко использовать в любом месте, без необходимости устанавливать какие-либо механизмы.
Мне нравится этот способ лучше:
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);
Ключ здесь, чтобы позвонить .then()
на deferred.promise
с объединенной версией массива рабочих элементов. это then
запускается после разрешения первоначального отложенного обещания, которое находится в fn для setTimeout. В более реалистичном сценарии отложенное обещание будет разрешено в обратном вызове http-клиента.
Начальный q.resolve(itemsToProcess)
отбрасывает вещи, передавая рабочие элементы первому вызову работы fn.
Я добавил это в надежде, что это поможет другим.
Я предлагаю другие решения, которые кажутся мне более понятными. Вы делаете то же самое, что и при создании цепочки обещаний напрямую: promise.then(doSomethingFunction).then(doAnotherThingFunction);
Если мы поместим это в цикл, мы получим это:
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) {
}
Мы используем функцию curry для использования нескольких аргументов. В нашем примереfunctionToCall.bind(this, arg1, arg2)
вернет функцию с одним аргументом: functionToCall(resultFromPreviousPromise)
Вам не нужно использовать результат предыдущего обещания.
Вот концепция конечного автомата, определенного с Q
,
Предположим, у вас определена функция HTTP, поэтому она возвращает Q
объект обещания:
var Q_http = function (url, options) {
return Q.when($.ajax(url, options));
}
Вы можете определить рекурсивную функцию nextState
следующим образом:
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);
});
}
куда function process(current, result)
это функция, чтобы узнать, каким будет следующий шаг в соответствии с current
состояние и result
из HTTP-вызова.
Когда вы используете его, используйте его так:
nextState(initial).then(function () {
// all requests are successful.
}, function (reason) {
// for some unexpected reason the request sequence fails in the middle.
});