Асинхронный цикл отложенных jQuery (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 и т.д. И журнал "все сделано".
Возможно, это действительно очевидно, но я пытался понять это во второй половине дня.
Ответы
Ответ 1
Я попробую использовать $().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');
Ответ 2
Для водопада вам нужен асинхронный цикл:
(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");
});
Ответ 3
Вы можете создать разрешенный $.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);
});
});
Лично я нахожу это чище, чем рекурсия и более знакомым, чем $(). Queue (jQuery API для $(). Queue сбивает с толку, так как он предназначен для анимации, также вероятно, что вы используете $.Deferred в других местах вашего код). Он также обладает преимуществами стандартной передачи результатов по водопаду с помощью resol() в асинхронной операции и позволяет присоединять свойство $.done.
Вот это в jsFiddle
Ответ 4
Посмотрите $. когда и then для запуска отложенных событий.
Водопады используются для возврата значений возврата от одного отложенного к следующему, последовательно. Это выглядело бы как-то как это.
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(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.
});
Ответ 5
Интересная задача действительно. То, что я придумал, - это рекурсивная функция, которая принимает список и необязательный начальный индекс.
Вот ссылка на 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);
});
Я не уверен, насколько велик ваш список функций, но просто имейте в виду, что браузер будет поддерживать ресурсы с первого вызова отложенногоSequentialDo, пока все не закончится.
Ответ 6
аргументы
- items: массив аргументов
- func: асинхронная функция
- обратный вызов: функция обратного вызова
- обновление: функция обновления
Простая петля:
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);
Отслеживание прогресса:
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);