6
\$\begingroup\$

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?

\$\endgroup\$
3
\$\begingroup\$

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);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.