3

When using jQuery promises sequentially, it is possible to chain them using then repeatedly:

e.g.

promise = promise.then(someoperation());

which also works inside a loop (very handy).

I have similar scenario where I needed to know when multiple parallel operations were completed, but not go through the coding overhead (e.g. added complexity) of creating an array of promises for the sole purpose of calling $.when.apply

After looking at my options, I came up with this pattern as an alternative:

promise = $.when(promise, anotherpromise);

To test it I came up with this test:

var p = $.Deferred().resolve().promise();

[1,2,3,4,5,6,7,8,9,10].forEach(function(i){
     p = $.when(p, delay(i,i * 500));
});

p.then(function(){
   log("All done");
});

JSFiddle: http://jsfiddle.net/TrueBlueAussie/0rh8Lhv4/1/

Which appears to work just fine, so I started applying it to other example on StackOverflow.

The next one I tried with this pattern was to fix the example from Pass in an array of Deferreds to $.when():

My code:

$("a").click(function () {
    var promise = GetSomeDeferredStuff();
    promise.then(function () {
        $("div").append("<p>All done!</p>");
    });
});

JSFiddle: http://jsfiddle.net/TrueBlueAussie/ts1dqwe3/1/

Q. For some reason this one never fires the final event. Can anyone spot the problem?

Update

Based on a comment from @Karl-André Gagnon, it seems the initial promise can just be undefined and still work. Much simpler:

e.g.

var p;
[1,2,3,4,5,6,7,8,9,10].forEach(function(i){
     p = $.when(p, delay(i,i * 500));
});
p.then(function(){
   log("All done");
});
Community
  • 1
  • 1
iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
  • You say creating an array of promises would be overhead. What do you think is the overhead of calling `$.when` multiple times?! – Bergi May 08 '15 at 21:39
  • Also, the result that your outermost promise eventually resolves with is totally f****ed up. – Bergi May 08 '15 at 21:41
  • @Bergi: I meant the coding overheads. Not the memory overhead. The overhead is a chain of promises instead of an array of promises. With regard to your second comment, can you clarify with technical wording? "Totally f****ed up" does not really do it for me :) – iCollect.it Ltd May 08 '15 at 23:25
  • I think the "coding overhead" is mitigated (or even reversed) by using `map` instead of `forEach`-type loops. With my second comment I meant to say that the value which `p` eventually resolves with (which should be an array of results, and is one with the reasonable solution) is totally unusable, being some kind of nested tuples (not even a cons list) – Bergi May 09 '15 at 16:25

2 Answers2

2

Okay, it turns out this pattern does indeed work just fine, but you need to ensure the initial promise you chain to is already resolved:

function GetSomeDeferredStuff() {
    var promise = $.Deferred().resolve().promise();

JSFiddle: http://jsfiddle.ne/TrueBlueAussie/ts1dqwe3/2/

In summary this pattern really is a simple alternative to creating an array just for use by $.when.apply.

Also, as pointed out by @Karl-André Gagnon, if you start with an undefined value, it does the same thing. Even better :)

function GetSomeDeferredStuff() {
    var promise;

JSFiddle: http://jsfiddle.net/TrueBlueAussie/ts1dqwe3/4/

iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
  • 2
    Why do you need to declared an empty deferred object first? Passing `undefined` to `when` just ignore it : http://jsfiddle.net/ts1dqwe3/3/. Did I miss something? – Karl-André Gagnon May 08 '15 at 19:17
  • 1
    @Karl-André Gagnon: Missed that little trick (updated). That makes the whole solution really neat now. Much simpler than $.when.apply on an array. Thanks. :) – iCollect.it Ltd May 08 '15 at 19:20
  • Something to consider is the number of promises created. With `$.when.apply($, array)`: **N + 1** promises. With looped `$.when(promise, ...)`: **2N** promises. Also, [consider the usefulness of the final result](http://jsfiddle.net/L0v40g6s/). – Roamer-1888 May 09 '15 at 05:40
  • @Roamer-1888: Yes, it probably is 2N, but that overhead is reasonable in many cases (ajax limits etc). As the for return value(s), that is worth investigating further to test the limitations of this technique. The results of an array used with `$.when.apply` are not typically used/usable either (e.g. not without generic processing of `arguments`). This is just about knowing when the operations are all complete. I shall continue to investigate the pros and cons of this approach and update what I find. – iCollect.it Ltd May 09 '15 at 17:42
  • @Roamer-1888: You may wish to critique this answer too, as it was considered "elegant", and is the pigeon pair to this problem: http://stackoverflow.com/questions/30096628/jquery-ajax-wait-each-function/30097071?s=1|9.0946#30097071 – iCollect.it Ltd May 09 '15 at 17:42
  • @TrueBlueAussie, I already saw that one. Nothing to offer there. – Roamer-1888 May 09 '15 at 18:36
0

Updated

I have similar scenario where I needed to know when multiple parallel operations were completed, but not go through the overhead of creating an array of promises for the sole purpose of calling

loop not utilized ; only $.when() ; same pattern as utilized when implementing through $.map() . Additionally , added "random" delay to jsfiddle ajax request ; promise.then() should not be called until all parallel function calls at $.when() completed , next called , queueName queue empty .

function GetSomeDeferredStuff(elem, name, cycles) {
    var el = (elem || {}),
        queueName = (name || "q"),
        len = (cycles || 5),
        request = function () {
            return $.ajax({
                type: "POST",
                url: '/echo/html/',
                data: {
                    html: "<p>Task #" + $.now() + " complete.",
                    delay: Math.random() * 5
                },
                success: function (data) {
                    $("div").append(data);
                }
            })
        }
    return $(el).queue(queueName, function (next) {
        return $.when(request()
                      , request()
                      , request()
                      , request()
                      , request())
                      .then(next)
    }).dequeue(queueName).promise(queueName);
}

$(function () {
    $("a").click(function () {
        var promise = GetSomeDeferredStuff();
        promise.then(function () {
            $("div").append("<p>All done!</p>");
        });
    });
});

jsfiddle http://jsfiddle.net/ts1dqwe3/10/


When using jQuery promises sequentially, it is possible to chain them using then repeatedly ... which also works inside a loop (very handy).

Try utilizing .queue() .promise(queueName)

function GetSomeDeferredStuff(elem, name, cycles) {
    // if no `elem`:`this` object passed , utilize empty object `{}`
    var el = (elem || {})
          // if no `name` passsed, utilize `String` `"q"`
        , queueName = (name || "q")
          // if no `cycles` how many functions passed to `.queue(queueName)`,
          // pass `5` functions to `.queue(queueName)`
        , len = (cycles || 5);
    return $(el).queue(queueName
    , $.map(Array(len), function (_, index) {
        return function (next) {
            return $.ajax({
                type: "POST",
                url: '/echo/html/',
                data: {
                    html: "<p>Task #" + (1 + index) + " complete.",
                    delay: (index + 1) / 2
                },
                success: function (data) {
                    return $("div").append(data);
                }
            // call "next" function in `queue`
            }).then(next)
        }
       // `.dequeue(queueName)` , return `queueName` jQuery promise object,
       // when all functions in `queue(queueName)` called ; 
       // `.queue(queueName)` empty
    })).dequeue(queueName).promise(queueName);
}

$(function () {
    $("a").click(function () {
        var promise = GetSomeDeferredStuff();
        promise.then(function () {
            // `this`:`elem` , or `{}`
            $("div").append("<p>All done!</p>");
        });
    });
});

jsfiddle http://jsfiddle.net/ts1dqwe3/6/

guest271314
  • 1
  • 15
  • 104
  • 177