Asynchronous Loop of jQuery Deferreds (promises)

荒凉一梦 提交于 2019-11-28 05:25:38

I'd try using $().queue instead of $.Deferred here. Add the functions to a queue, and only call the next one when ready.

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

For a waterfall, you need an async loop:

(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");
});

You can create a resolved $.Deferred and just add to the chain with each iteration:

var dfd = $.Deferred().resolve();
tasks.forEach(function(task){
    dfd = dfd.then(function(){
        return doTask(task);
    });
});

Step by step the following is happening:

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

});

Personally, I find this cleaner than recursion and more familiar than $().queue (jQuery API for $().queue is confusing as it is designed for animations, it is also likely you are using $.Deferred's in other places in your code). It also has the benefits of standard transfer of results down the waterfall through resolve() in the async operation and allowing the attachment of a $.done property.

Here it is in a jsFiddle

Have a look at the $.when and then methods for running deferreds.

Waterfalls are used to pipe return values from one deferred to the next, in series. It would look something 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"); });

Note the argument passed to resolve. That gets passed to the next function in the chain. If you just want to run them in series without piping in arguments, you can take that out and change the reduce call to .reduce(function(chain, taskNum) { return chain.then(doTask.bind(null, taskNum)); }, doTask(tasks[0]));

And in parallel it would look 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.
});

Interesting challenge indeed. What I have come up with is a recursive function that accepts a list and an optional start index.

Here is a link to the jsFiddle that I have tested with a few different list lengths and intervals.

I'm assuming you have a list of functions that return promises (not a list of numbers). If you do have a list of numbers you would change this part

$.when(tasks[index]()).then(function(){
    deferredSequentialDo(tasks, index + 1);
});

to this

/* 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);
});

I'm not sure how big your list of functions could be but just be aware that the browser will hold on to the resources from the first deferredSequentialDo call until they are all finished.

Arguments

  • items: array of arguments
  • func: the async function
  • callback: callback function
  • update: update function

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);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!