Асинхронный цикл jQuery Deferreds (обещания)
Я пытаюсь создать то, что, я думаю, называется "Водопад". Я хочу последовательно обрабатывать массив асинхронных функций (обещания jQuery).
Вот надуманный пример:
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 и т. Д. И журнал "все выполнено".
Может быть, это действительно очевидно, но я пытался выяснить это весь день.
5 ответов
Я бы попробовал использовать $().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');
Для водопада вам нужен асинхронный цикл:
(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);
});
});
Лично я нахожу это чище, чем рекурсия и более знакомым, чем $(). Queue (jQuery API для $(). Queue сбивает с толку, так как он предназначен для анимации, также вероятно, что вы используете $.Deferred в других местах вашего код). Он также обладает преимуществами стандартной передачи результатов по водопаду с помощью resol () в асинхронной операции и позволяет присоединять свойство $.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(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.
});
Аргументы
- 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);
Действительно интересный вызов. Я придумал рекурсивную функцию, которая принимает список и необязательный начальный индекс.
Вот ссылка на jsFiddle, который я протестировал с несколькими разными длинами списка и интервалами.
Я предполагаю, что у вас есть список функций, которые возвращают обещания (не список чисел). Если у вас есть список номеров, вы бы изменили эту часть
$.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, пока они не будут завершены.