如何进行可变数量的调用,每个调用按顺序异步返回?
该场景是一组HTTP请求,其数量和类型由第一个HTTP请求的结果确定。
我想简单地这样做。
我也看到this answer建议这样的事情:
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”的新函数来抽象递归。什么是更好的方式?
答案 0 :(得分:75)
使用[].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")
所做的那样)来启动。
起初可能需要一段时间来围绕这里发生的事情,但如果你花一点时间来完成它,那么这是一个易于使用的模式,无需设置任何机器。
答案 1 :(得分:1)
我更喜欢这种方式:
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
在初始延迟的promise解析之后运行,它在setTimeout的fn中。在更现实的情况下,延迟的承诺将在http客户端回调中得到解决。
最初的q.resolve(itemsToProcess)
通过将工作项传递给工作fn的第一次调用来解决问题。
我补充说希望它可以帮助别人。
答案 2 :(得分:1)
以下是使用Q
定义的状态机的概念。
假设您已定义HTTP函数,因此它返回一个Q
promise对象:
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
状态和HTTP调用中的result
找出下一步的内容。
使用时,请使用它:
nextState(initial).then(function () {
// all requests are successful.
}, function (reason) {
// for some unexpected reason the request sequence fails in the middle.
});
答案 3 :(得分:1)
我提出了另一种解决方案,对我来说更容易理解。
您可以像直接链接承诺一样:
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) {
}
我们使用function currying来使用多个参数。在我们的例子中
functionToCall.bind(this, arg1, arg2)
将返回一个带有一个参数的函数:functionToCall(resultFromPreviousPromise)
您不需要使用先前承诺的结果。