Josh
Josh

Reputation: 3442

Deferred, Promises, and Ajax not going in correct order

I'm trying to set up a loop of Ajax calls and a function that runs after all the Ajax calls have resolved. I read through a bunch of SO questions on this problem and thought I was able to apply it to my code, but it isn't working and I can't for the life of me figure out why.

Here's the JS code. The map function runs 10 times FYI.

function addData(data) {
    var deferreds = [];
    $.map(data, function(job) {
        deferreds.push(addLocation(job));
    });

    console.log(deferreds);
    $.when.apply($, deferreds).then(mapData(data));
}

function addLocation(job) {
    var dfd = $.Deferred(),
        url = url;

    console.log('in the addLocation function, outside the ajax call');
    $.ajax({
        url: url,
        dataType: 'jsonp',
        jsonp: 'json_callback'
    }).done(function(location) {
        job.lnglat = [parseFloat(location[0].lon), parseFloat(location[0].lat)];
        console.log('resolved location');
        dfd.resolve();
    });

    return dfd.promise();
}

function mapData(data) {
    console.log('in the mapData function');
    console.log(data);
    var point = svg.selectAll('points')
        .data(data);

    point.exit().remove();
    point.enter().append('circle')
        .attr('r', 2.5);

    point
        .attr('cx', function(d) {
            console.log(d);   //shows d and has the lnglat but I think that is due to the developer tools reading the final result, not the result at the time
            console.log(d.lnglat);   // this gives undefined
            console.log(d.lnglat[0]); // this gives an error as it hasn't been defined
            console.log(projection(d.lnglat[0]));
            return projection(d.lnglat[0])[0];
        })
        .attr('cy', function(d) {
            return projection(d.lnglat[1])[1];
        });
}

The Chrome Developer tools reports the following order:

in the addLocation function, outside the ajax call  //x10
[Object, object...] //this is the object list of deferred objects, there are 10 of them
in the mapData function
[Object, object..] //this is the object list of data from the mapData function
Object > ... //this is the d object in the mapData function > point anonymous function
undefined // this is the result of d.lnglat
Uncaught typeError ... // this is the result of d.lnglat[0] which doesn't exist yet
resolvedLocation //x10 this should be a lot higher right?

So I want the resolvedLocation console.log to run prior to the in the mapData function to run and I thought I had set it up that way, but it obviously isn't working. What am I missing?

Upvotes: 0

Views: 54

Answers (1)

Aviv Shaked
Aviv Shaked

Reputation: 762

As adeneo said in his comment, the problem here is the imidiate invocation of mapData in

$.when.apply($, deferreds).then(mapData(data))

To solve this you need to use bind to keep most of you solution intact (and not change it much). This should work:

$.when.apply($, deferreds).then(mapData.bind(this, data))

The bind does not delay the invocation of the function. It binds the data argument to the invocation of the function, so when 'then' function invokes mapData, it does so with 'data' as its argument, and that happens only after all promises are resolved.

Upvotes: 1

Related Questions