Why does promises get resolved before callbacks succeed?

So I have a code similar to this

var addExpenses = function(parent) {
    return function(data) {
        if (data) {
            for (var i = 0; i < data.length; i++) {
                var expenditure = data[i].RestrictedExpenditure;
                expenditure = parseFloat(expenditure);
                var child = {level: 4, name: data[i].Name, size: expenditure, children: []};
                parent.children.push(child);
            }
        }
    }
}

var addSubActivities = function(parent) {
    return function(data) {
        if (data) {
            for (var i = 0; i < data.length; i++) {
                var expenditure = data[i].Expenditure;
                expenditure = parseFloat(expenditure);
                if (expenditure < 0) {
                    expenditure = -expenditure;
                }
                var child = {level: 3, name: data[i].Name, size: expenditure, children: []};
                parent.children.push(child);
                subActivitiesDfds.push($.getJSON("http://localhost:8080/district/1/subActivities/" + data[i].Code + "/expenses", addExpenses(child)));
                // subActivitiesDfds.push(dfd);
            }
        }
    }
}

for (var i = 0; i < data.length; i++) {
    var expenditure = data[i].Expenditure;
    expenditure = parseFloat(expenditure);
    total += expenditure;
    var child = {level: 2, name: data[i].Name, size: expenditure, children: []};
    root.children.push(child);

    subActivitiesDfds.push($.getJSON("http://localhost:8080/district/1/activities/" + data[i].Code + "/subActivities", addSubActivities(child)));
    // subActivitiesDfds.push(dfd);
}
root.size = total;


$.when.apply($, subActivitiesDfds).done(function() {

      

var nodes = partition.nodes (root); // render

I am getting an error on this line -

parent.children.push(child);

      

  • children are undefined. This is because partition.nodes (root) destroys the parent property, which happens because the promises list is resolved prior to calling callback addExpenses.

Why?

+3


source to share


1 answer


The problem arises because $.when.apply($, subActivitiesDfds)

subActivitiesDfds

only the top-level promises are filled at runtime . The code that generates promises for the next level is only run as the top level promises are executed.

To fix you need:

  • to ensure that it addSubActivities()

    returns a promise when all processing is complete.
  • to replace $.getJSON(..., fn)

    for $.getJSON(..., ).then(fn)

    in two places.

You will also find it array.map()

handy for constructing arrays of promises.



Try:

var addExpenses = function(parent) {
    return function(data) {
        if (data) {
            for (var i = 0; i < data.length; i++) {
                var child = {
                    level: 4, 
                    name: data[i].Name, 
                    size: parseFloat(data[i].RestrictedExpenditure),
                    children: []
                };
                parent.children.push(child);
            }
        }
    }
}

var addSubActivities = function(parent) {
    return function(data) {
        if (data) {
            var promises = data.map(function(item) {
                var child = {
                    level: 3,
                    name: item.Name,
                    size: Math.abs(parseFloat(item.Expenditure)),
                    children: []
                };
                parent.children.push(child);
                return $.getJSON("http://localhost:8080/district/1/subActivities/" + item.Code + "/expenses")
                .then(addExpenses(child));
            });
            return $.when.apply($, promises);
        } else {
            return $.when();//resolved promise
        }
    }
}

var promises = data.map(function(item) {
    var child = {
        level: 2,
        name: item.Name,
        size: parseFloat(item.Expenditure),
        children: []
    };
    root.children.push(child);
    total += child.size;
    return $.getJSON("http://localhost:8080/district/1/activities/" + item.Code + "/subActivities").then(addSubActivities(child));
});
root.size = total;

$.when.apply($, promises).then(function() {
    var nodes = partition.nodes(root); //render
});

      

The top-level array is promises

still only populated by the top-level promises, but now the chaining .then()

ensures that each of these promises will only be counted when its entire synchronous and asynchronous sub-task is complete.

+1


source







All Articles