1

I have an array of JS resources I need to request. The order must be maintained, and the responses must be in the order that I request them in. The following requests each one in sequence, but I cannot capture the .done() promise callback on the entire request.

I call the getScripts method from my object:

var depArray = ['file1.js', 'file2.js', 'file3.js'];
getScripts(depArray, global_config.jspath + "/");

This iterates over the array within a $.Deferred() a nested $.when promise...

    getScripts : function(arr, path) {
        var depMap = [];
        $.Deferred(
            $.map(arr, function(src) {
                $.when(
                    $.ajax({
                            url: (path || "") + src + ".js",
                            success: function (data) {
                                depMap.push(data);
                                //return data;
                            }
                        }
                    )
                ).then(function() {
                    console.log("request complete...", (path || "") + src + ".js");
                    return true;
                });
            })
        ).done(
            function() { alert('completely done!!!!'); }
        );
    },

The alert('completely done!!!!') is fired immediately, not waiting for the ajax calls to execute and finish. I need to return the depMap array from the method, so I have a map of all the responses, in order, from the call.

The response would be:

['contents from file1.js', 'contents from file2.js', 'contents from file3.js']

But I never have a depMap to hand off at the end. Promises are still a bit baffling to me. Any help is appreciated...

gen_Eric
  • 223,194
  • 41
  • 299
  • 337
Cwest
  • 23
  • 4

2 Answers2

2

Ok, some cleanup is needed. First of all, remove $.Deferred - it's not needed since you already have $.when promise. Then you need to construct array of promises and pass it into $.when.

Finally, it can look like this:

getScripts: function(arr, path) {
    // map each promise to its resource and aggregate
    return $.when.apply(null, arr.map(function(src) {
        return $.ajax({url: (path || "") + src + ".js"});
    })).then(function(){
        return [].map.call(arguments, function(x){ return x[0]; }) // just the data
    });
}

Demo: http://plnkr.co/edit/DvBj9PcaTQzskCRIUAHZ?p=preview

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
dfsq
  • 191,768
  • 25
  • 236
  • 258
  • There's no guarantee the ajax calls will return in the order they were invoked, so the code in your answer cannot guarantee the order of the items in the `depMap` array. – John S Sep 09 '15 at 18:55
  • Yes, good catch. I initially wanted to post answer with real promises, that's why I wrote it like this, didn't fix it for jQuery. – dfsq Sep 09 '15 at 19:12
  • "real promises"? Are you referring to promises from a different library? – John S Sep 09 '15 at 19:16
  • I mean real Promises/A+ complaint implementations. – dfsq Sep 09 '15 at 19:20
  • I want to enforce that the ajax calls are done in sequence with the use of promises. One cannot finish before the other. I understand that you cannot guarantee the order of arrays, but given a map.. the ajax/getScript calls need to be requested in order. – Cwest Sep 09 '15 at 19:29
  • @Bergi I'm aware of it, but in this case I was avoiding poor jQuery promise implementation so I had to use it. – dfsq Sep 09 '15 at 21:22
  • @dfsq: I see that you only wanted a single result parameter, but `return $.ajax(…).then(function(data) { return data; })` would have sufficed for that. And even if you were aware of it, you still forgot to propagate errors. – Bergi Sep 09 '15 at 21:24
  • @Benjamin Gruenbaum Thanks for the edit! This is strange, I surely tried proper code before my dirty jQuery deferred hack. Must have missed something little then. – dfsq Sep 10 '15 at 09:41
1

The $.ajax() function returns a jqXHR object, which is a Promise. The $.when() function says it takes one or more Deferred objects, but it appears to require that they only be Promises, not full Deferred objects. Therefore, you do not need to use $.Deferred() to create Deferred objects for each ajax request. You can just use the objects returned by $.ajax(). You can use $.map() to create an array of the promises, in the order the ajax calls were made.

Ultimately though, your getScripts() function cannot return the depMap array because it is asynchronous. Instead, it could be written to return a Promise, as shown below.

getScripts: function(arr, path) {
    return $.when.apply($, $.map(arr, function(src) {
        return $.ajax({ url: (path || '') + src + '.js' });
    })).then(function() {
        return (arr.length > 1)
            ? $.map(arguments, function(a) { return a[0]; })
            : [arguments[0]];
    });
},

For information on how the array returned from the $.when(...).then() function is constructed from the arguments to its callback function, see this Stackoverflow answer. (The same info for .done() applies to .then().)

Demo on jsfiddle

Community
  • 1
  • 1
John S
  • 21,212
  • 8
  • 46
  • 56