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?
source to share
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.
source to share