jQuery的异步循环deferred (promises)

Asynchronous Loop of jQuery Deferreds (promises)

本文关键字:promises deferred 循环 异步 jQuery      更新时间:2023-09-26

我正在尝试创造我认为被称为"瀑布"的东西。我想按顺序处理异步函数数组(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,等等,日志"所有完成"。

也许这真的很明显,但我已经想了一下午了

我会尝试在这里使用$().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");
});

可以创建一个解析过的$。延迟,每次迭代只添加到链中:

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。队列是令人困惑的,因为它是为动画设计的,它也可能是你使用$。Deferred在代码的其他地方)。它还具有通过异步操作中的resolve()将结果按瀑布式标准传输的优点,并允许附加$。做财产。

在jsFiddle中

看一下$。When和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调用更改为.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,我已经测试了几个不同的列表长度和间隔。

我假设你有一个返回承诺的函数列表(不是数字列表)。如果你有一个数字列表,你可以修改这部分

$.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调用中保留资源,直到它们全部完成。

参数

  • items:参数数组
  • func:异步函数
  • callback:回调函数
  • update:更新功能

简单的循环:

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);