jQuery Deferreds的异步循环(promises)

我试图创造我认为被称为“瀑布”的东西。 我想顺序处理一系列异步函数(jQuery promises)。

这是一个人为的例子:

function doTask(taskNum){ var dfd = $.Deferred(), time = Math.floor(Math.random()*3000); setTimeout(function(){ console.log(taskNum); dfd.resolve(); },time) return dfd.promise(); } var tasks = [1,2,3]; for (var i = 0; i < tasks.length; i++){ doTask(tasks[i]); } console.log("all done"); 

我希望它按照它们执行的顺序完成任务(存在于数组中)。 因此,在这个例子中,我希望它执行任务1并等待它解决然后执行任务2等待它解决,执行任务3等并且日志“全部完成”。

也许这是非常明显的,但我一直试图在整个下午解决这个问题。

我尝试使用$().queue而不是$.Deferred here。 将函数添加到队列中,并在准备好时仅调用下一个函数。

 function doTask(taskNum, next){ var time = Math.floor(Math.random()*3000); setTimeout(function(){ console.log(taskNum); next(); },time) } function createTask(taskNum){ return function(next){ doTask(taskNum, next); } } var tasks = [1,2,3]; for (var i = 0; i < tasks.length; i++){ $(document).queue('tasks', createTask(tasks[i])); } $(document).queue('tasks', function(){ console.log("all done"); }); $(document).dequeue('tasks'); 

对于瀑布,您需要一个异步循环:

 (function step(i, callback) { if (i < tasks.length) doTask(tasks[i]).then(function(res) { // since sequential, you'd usually use "res" here somehow step(i+1, callback); }); else callback(); })(0, function(){ console.log("all done"); }); 

您可以创建已解析的$ .Deferred,并在每次迭代时添加到链中:

 var dfd = $.Deferred().resolve(); tasks.forEach(function(task){ dfd = dfd.then(function(){ return doTask(task); }); }); 

一步一步发生以下情况:

 //begin the chain by resolving a new $.Deferred var dfd = $.Deferred().resolve(); // use a forEach to create a closure freezing task tasks.forEach(function(task){ // add to the $.Deferred chain with $.then() and re-assign dfd = dfd.then(function(){ // perform async operation and return its promise return doTask(task); }); }); 

就个人而言,我觉得这比递归更干净,比$()。队列(jQuery API for $()。队列更令人困惑,因为它是为动画而设计的,你也可能在你的其他地方使用$ .Deferred’s码)。 它还具有以下优点:通过异步操作中的resolve()并允许附加$ .done属性,在瀑布下标准地传输结果。

这是一个jsFiddle

看看$ .when 然后运行deferreds的方法。

瀑布用于将从一个延迟到下一个的返回值串行管道。 它看起来像这样 。

 function doTask (taskNum) { var dfd = $.Deferred(), time = Math.floor(Math.random() * 3000); console.log("running task " + taskNum); setTimeout(function(){ console.log(taskNum + " completed"); dfd.resolve(taskNum + 1); }, time) return dfd.promise(); } var tasks = [1, 2, 3]; tasks .slice(1) .reduce(function(chain) { return chain.then(doTask); }, doTask(tasks[0])) .then(function() { console.log("all done"); }); 

注意传递给resolve的参数。 这将传递给链中的下一个函数。 如果你只想在没有参数管道的情况下串行运行它们,你可以把它拿出来并将reduce调用更改为.reduce(function(chain, taskNum) { return chain.then(doTask.bind(null, taskNum)); }, doTask(tasks[0]));

并行它看起来像这样 :

 var tasks = [1,2,3].map(function(task) { return doTask(task); }); $.when.apply(null, tasks).then(function() { console.log(arguments); // Will equal the values passed to resolve, in order of execution. }); 

确实有趣的挑战。 我提出的是一个递归函数,它接受一个列表和一个可选的起始索引。

这是一个jsFiddle的链接 ,我已经测试了几个不同的列表长度和间隔。

我假设你有一个返回promises的函数列表(不是数字列表)。 如果您有一个数字列表,您将更改此部分

 $.when(tasks[index]()).then(function(){ deferredSequentialDo(tasks, index + 1); }); 

对此

 /* Proxy is a method that accepts the value from the list and returns a function that utilizes said value and returns a promise */ var deferredFunction = myFunctionProxy(tasks[index]); $.when(tasks[index]()).then(function(){ deferredSequentialDo(tasks, index + 1); }); 

我不确定你的function列表有多大,但只要知道浏览器将保留第一个deferredSequentialDo调用的资源,直到它们全部完成。