HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Multiple jQuery promises

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
promisesjquerymultiple

Problem

I'm starting to reuse this pattern, and was wondering if there was a more succinct/clear way to write it.

Given a function foo taking a callback argument:

function foo(..., cb) { ... }


And function bar which calls foo with several different arguments taking a callback function of its own to be called when all the 'dependent' callbacks have been called, I write:

function bar(data, cb) {
    var promises = [];
    function createPromise() {
         var p = $.Deferred();
         promises.push(p);
         return function() { p.resolve(); };
    };
    for (var i = 0; i < data.length; i++) {
         foo(data[i], createPromise());
    }
    $.when.apply($, promises).then(cb);
}


I'm quite happy with the pattern, but the apply and closure-returning-a-function make me suspect that there might be a simpler way of doing the same... Ideas?

Solution

Well, first of all, your closure doesn't have to create a function. This will work just as well:

function createPromise() {
     var p = $.Deferred();
     promises.push(p);
     return p.resolve; // Doesn't create an anonymous function
};


Second of all, if you're doing this pattern a lot, I would make a generic method that lets you convert any function that accepts a callback to one that returns a promise:

function callbackToPromise(func) {
    return function() {
        var args = $.makeArray(arguments);

        var p = $.Deferred();
        args.push(p.resolve); // Add the deferred as a callback

        func.apply(this, args);

        return p.promise();
    };
}

var improvedFoo = callbackToPromise(foo);

function bar(data, cb) {
    var promises = [];
    for (var i = 0; i < data.length; i++) {
         promises.push(improvedFoo(data[i]));
    }
    $.when.apply($, promises).then(cb);
}


$.when really should accept an array of Deferred's as an argument, but until that feature is added, I don't think there's a way to avoid using .apply here, except to add another helper function:

function when(promises) {
    return $.when.apply($, promises);
}

function bar(data, cb) {
    var promises = [];
    for (var i = 0; i < data.length; i++) {
         promises.push(improvedFoo(data[i]));
    }
    when(promises).then(cb);
}


A slightly less-readable version can eliminate the promises variable entirely, though I prefer the more-readable version above:

function bar(data, cb) {
    when($.map($.makeArray(data), function(item) {
        return improvedFoo(item);
    })).then(cb);
}

Code Snippets

function createPromise() {
     var p = $.Deferred();
     promises.push(p);
     return p.resolve; // Doesn't create an anonymous function
};
function callbackToPromise(func) {
    return function() {
        var args = $.makeArray(arguments);

        var p = $.Deferred();
        args.push(p.resolve); // Add the deferred as a callback

        func.apply(this, args);

        return p.promise();
    };
}

var improvedFoo = callbackToPromise(foo);

function bar(data, cb) {
    var promises = [];
    for (var i = 0; i < data.length; i++) {
         promises.push(improvedFoo(data[i]));
    }
    $.when.apply($, promises).then(cb);
}
function when(promises) {
    return $.when.apply($, promises);
}

function bar(data, cb) {
    var promises = [];
    for (var i = 0; i < data.length; i++) {
         promises.push(improvedFoo(data[i]));
    }
    when(promises).then(cb);
}
function bar(data, cb) {
    when($.map($.makeArray(data), function(item) {
        return improvedFoo(item);
    })).then(cb);
}

Context

StackExchange Code Review Q#8809, answer score: 3

Revisions (0)

No revisions yet.