我正在努力创造我认为被称为"瀑布"。我想顺序处理一系列异步函数(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等,并且日志&#34;全部完成&#34;。
也许这是非常明显的,但我整个下午都试图解决这个问题。
答案 0 :(得分:20)
我在这里尝试使用$().queue
代替$.Deferred
。将函数添加到队列中,并在准备好时仅调用下一个函数。
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');
答案 1 :(得分:9)
对于瀑布,您需要一个异步循环:
(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");
});
答案 2 :(得分:6)
您可以创建已解决的$ .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&#39 ; s在你的代码的其他地方)。它还具有以下优势:通过异步操作中的resolve()并允许附加$ .done属性,在瀑布下标准地传输结果。
这是jsFiddle
答案 3 :(得分:5)
瀑布用于将返回值从一个延迟管道输出到下一个。看起来像like this。
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]));
并行地看起来like this:
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.
});
答案 4 :(得分:0)
Here is a link to the 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);
});
我不确定您的功能列表有多大,但请注意浏览器将保留第一个deferredSequentialDo调用的资源,直到它们全部完成。
答案 5 :(得分:0)
Arguments
Simple Loop:
var syncLoop = function(items, func, callback) {
items.reduce(function(promise, item) {
return promise.then(func.bind(this, item));
}, $.Deferred().resolve()).then(callback);
};
syncLoop(items, func, callback);
Track Progress:
var syncProgress = function(items, func, callback, update) {
var progress = 0;
items.reduce(function(promise, item) {
return promise.done(function() {
update(++progress / items.length);
return func(item);
});
}, $.Deferred().resolve()).then(callback);
};
syncProgress(items, func, callback, update);