如何在Q中链接可变数量的promise,依次?
在Q中 ,我已经看到Chaining中的任意数量的承诺 ; 我的问题是不同的。
我怎样才能进行可变数量的调用,每个调用都是asynchronous返回的?
场景是一组HTTP请求,其数量和types由第一个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似乎很尴尬。 或者定义一个叫做“循环”的新函数来抽象recursion。 什么是更好的方法?
用[].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);
这里的关键是使用工作项数组的拼接版本在deferred.promise
上调用.then()
。 then
在初始的延迟承诺parsing之后运行,这在setTimeout的fn中。 在更现实的情况下,延迟承诺将在http客户端callback中得到解决。
最初的q.resolve(itemsToProcess)
通过将工作项目传递给工作fn的第一个调用来解决问题。
我希望这会帮助别人。
这是一个用Q
定义的状态机的概念。
假设你定义了HTTP函数,所以它返回一个Q
promise对象:
var Q_http = function (url, options) { return Q.when($.ajax(url, options)); }
你可以定义一个recursion函数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. });
我提出了另一个解决scheme,这对我来说更容易理解。 你可以像直接链接诺言一样做: 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) { }
我们使用函数currying来使用多个参数。 在我们的例子中, functionToCall.bind(this, arg1, arg2)
将返回一个带有一个参数的functionToCall(resultFromPreviousPromise)
: functionToCall(resultFromPreviousPromise)
你不需要使用前一个promise的结果。