I have a function which essentially creates an object and then pushes each newly created object to an array of objects. However I am facing an issue where only the last object being created (in my for loop) is being pushed to my object array.
I firstly declare my object and object array:
this.locationObjectArray = [];
this.locationObject = {};
I then declare a variable "that" equal to "this" so that I can use the above declared object and object array in my callbacks:
var that = this;
I then read an odata entity which returns a success callback with the specified entities data:
oModel.read("/Customers", {
success: function (resp) {
var promises = [];
for (var x = 0; x < resp.results.length; x ) {
// Set tooltip property
var tooltip = resp.results[x].Country;
that.locationObject.tooltip = tooltip;
// Set type property
that.locationObject.type = "Inactive";
// Set position property
var request = $.ajax({
url: "https://api.opencagedata.com/geocode/v1/json?key=d3e40bf9b50247d4acf8122c543c5187&q=" tooltip,
type: "GET",
dataType: 'json',
success: function (resp) {
that.locationObject.pos = resp.results[0].geometry.lng ";" resp.results[0].geometry.lat ";0";
}
});
// Populate promises and locationObjectArray
that.locationObjectArray.push(that.locationObject);
promises.push(request);
console.log(that.locationObject);
}
$.when.apply(null, promises).done(function(){
// console.log(that.locationObjectArray);
that.getView().getModel("map").setProperty("/Spots", that.locationObjectArray);
})
}
})
I created an array called promises as in my for loop I am making an Ajax request to an API which will return a promise each time it loops. I need to contain each request (promise) so that I can set the remainder of the function to run only once all of the data requested in each request (of each loop) is received.
I then create my for loop.
Within the for loop I begin to set each property of the previously declared "locationObject". The properties are: "tooltip" (locationObject.tooltip), "type" (locationObject.type) and "pos" (locationObject.pos). Both properties "tooltip" and "type" are straight forward. It is only the property "pos" that is causing difficulties as this data is returned as the result of a promise success callback in each loop.
Nevertheless, I then push the newly created object to the object array and also push that loops promise to the promises array.
Once the for loop has finished and all promises are resolved, I then set my data model.
The above would appear as fine, although the resulting object array (locationObjectArray) comes out like this:
The entire array is populated (for each loop) with the data collected in the locationObject for the final loop.
I have attempted to reset the object (locationObject) at the beginning of each loop:
...
for (var x = 0; x < resp.results.length; x ) {
that.locationObject = {};
...
However that interferes with the Ajax request to get the "pos" property data for each object, and the resulting array is constructed successfully with only the "tooltip" and "type" properties set:
I am not sure what I could do to get around this, or why the promises are now ignored, any suggestions / advice will be appreciated.
CodePudding user response:
There are several issues in this code. The main one I see is that you only have one locationObject
that you keep modifying and pushing into the array over and over again. Since .push()
pushes a reference to that object (does not make a copy), you just end up with an array of all the same object in it and it has the value from the last item that you processed.
You can fix that main issue by just creating a new locationObject
in your loop. Here's what I would suggest for a rewrite:
oModel.read("/Customers", {
success: async function(resp) {
for (let result of resp.results) {
const locationObject = {};
const tooltip = result.Country;
locationObject.type = "Inactive";
const geoData = await $.ajax({
url: "https://api.opencagedata.com/geocode/v1/json?key=d3e40bf9b50247d4acf8122c543c5187&q=" tooltip,
type: "GET",
dataType: 'json',
});
locationObject.pos = geoData.results[0].geometry.lng ";" geoData.results[0].geometry.lat ";0";
that.locationObjectArray.push(that.locationObject);
}
that.getView().getModel("map").setProperty("/Spots", that.locationObjectArray);
}
});
This uses the promise that $.ajax()
returns and gets rid of the success
callback for $.ajax()
. It also simplifies the code by serializing the for
loop with await
.
If you want to run all the ajax calls in parallel and avoid using await
, you can do it this way:
oModel.read("/Customers", {
success: function(resp) {
Promise.all(resp.results.map(result => {
const locationObject = {};
const tooltip = result.Country;
locationObject.type = "Inactive";
return $.ajax({
url: "https://api.opencagedata.com/geocode/v1/json?key=d3e40bf9b50247d4acf8122c543c5187&q=" tooltip,
type: "GET",
dataType: 'json',
}).then(geoData => {
locationObject.pos = geoData.results[0].geometry.lng ";" geoData.results[0].geometry.lat ";0";
return locationObject;
});
})).then((locationArray) => {
that.getView().getModel("map").setProperty("/Spots", locationObjectArray);
});
}
});
Note, this avoids using that.locationObject
and that.locationObjectArray
entirely. The results are collected and used internally without those higher scoped variables.