【问题标题】:Asynchronous Loop of jQuery Deferreds (promises)jQuery Deferreds 的异步循环(promises)
【发布时间】:2013-03-19 16:21:15
【问题描述】:

我正在尝试创建我认为被称为“瀑布”的东西。我想按顺序处理一组异步函数(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 等,然后记录“全部完成”。

也许这真的很明显,但我整个下午都在试图弄清楚这一点。

【问题讨论】:

    标签: javascript jquery jquery-deferred waterfall


    【解决方案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 更熟悉($().queue 的 jQuery API 令人困惑,因为它是为动画设计的,您也可能在其他程序中使用 $.Deferred代码中的位置)。它还具有通过异步操作中的 resolve() 将结果标准传输到瀑布的好处,并允许附加 $.done 属性。

      这里是jsFiddle

      【讨论】:

        【解决方案4】:

        查看运行延迟的$.whenthen 方法。

        瀑布用于将返回值从一个延迟传递到下一个,串联。它看起来像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.
        });
        

        【讨论】:

        • 这样使用reduce和map超优雅
        【解决方案5】:

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

        Here is a link to the 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 调用中的资源,直到它们全部完成。

        【讨论】:

        • deferredSync 听起来有点矛盾
        • 确实如此,但是如果您有几个要同步执行的 ajax 调用,我可以看到它的用途(我实际上在我的经验中遇到过)
        • Ajax 不同步的。什么意思?
        • 我知道它不是同步的,但是如果 ajax 调用 #2 依赖于 ajax 调用 #1 的结果,那么它们需要一个接一个地执行,而不是同时执行。
        • 是的,我的错,我的大脑顺序和同步地一起崩溃了。更新了我的答案和 jsFiddle。
        【解决方案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);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-04-05
          • 1970-01-01
          • 2018-10-17
          • 1970-01-01
          • 2018-09-18
          • 1970-01-01
          • 2014-03-14
          相关资源
          最近更新 更多