Multiple returns from a callback in javascript - javascript

I am using getJSON to grab data from a database, which I am then displaying to a map using Leaflet.
I want to use multiple getJSON calls that I then display as different layers on the same map. My problem is how to get the callback working with multiple geoJSON calls in the getData function. Or alternatively (I'm unsure which is the better approach) - having multiple versions of the getData function and then being able to access all of them to form the layers.
For a single getJSON the following works:
function getData(callback) {
$.getJSON("getData.php", callback );
}
getData(function(data) {
let markerlist = [];
for (let i = 0; i< data.length; i++) {
let location = new L.LatLng(data[i].siteLat, data[i].siteLon);
let marker = new L.Marker(location, {
icon: myIcon,
title: 'thetitle' });
markerlist.push(marker);
}
let myLayerGroup = L.layerGroup(markerlist) // create the layer
map.addLayer(myLayerGroup); // add the layer to the map
var overlays = {"layername":myLayerGroup};
L.control.layers(baseLayers,overlays).addTo(map);
});
I have tried to follow Need callback returns multiple values in nodejs
, which looks similar, but with no success.
I tried:
function getData(callback) {
let callbackString = {};
$.getJSON("getData.php", callbackString.set1);
$.getJSON("getOtherData.php", callbackString.set2);
callback(null,callbackString);
}
getData(function(data) {
let data1 = data.set1;
let data2 = data.set2;
let markerlist = [];
for (let i = 0; i< data1.length; i++) {
let location = new L.LatLng(data1[i].siteLat, data1[i].siteLon);
let marker = new L.Marker(location, {
icon: myIcon,
title: 'thetitle' });
markerlist.push(marker);
}
let myLayerGroup = L.layerGroup(markerlist) // create the layer
map.addLayer(myLayerGroup); // add the layer to the map
var overlays = {"layername":myLayerGroup};
L.control.layers(baseLayers,overlays).addTo(map);
});
which gave the error TypeError: null is not an object (evaluating 'data.set1')
I do not know where to start to have multiple versions of the getData and then access all the info in the data function.

This code
let callbackString = {};
$.getJSON("getData.php", callbackString.set1);
$.getJSON("getOtherData.php", callbackString.set2);
callback(null,callbackString);
.set1 and .set2 will be undefined, because you just created callbackString as an empty object! I think you've misunderstood the purpose of the second argument to getJSON ... that's data that is sent in the request
You're also calling callback with the first argument as null - yet you're trying to use getData like
getData(function(data) { ...
therefore, data will always be null
Also, $.getJSON is asynchronous and your code does not wait for the request to complete - therefore you'd have no chance of accessing the results
Perhaps this will help
function getData(callback) {
$.when($.getJSON("getData.php"), $.getJSON("getOtherData.php")).then(function(set1, set2) {
callback({set1:set1, set2:set2});
});
}
however, if you want proper error handling, then you may do something like
function getData(callback) {
$.when($.getJSON("getData.php"), $.getJSON("getOtherData.php"))
.then(function(set1, set2) {
callback(null, {set1:set1, set2:set2});
})
.catch(function(err) {
callback(err);
});
}
getData(function(err, data) {
if (err) {
//handle error
} else {
let data1 = data.set1;
let data2 = data.set2;
let markerlist = [];
...
...
}
});
Personally, because $.getJSON returns a Promise (well, jQuery's version of a promise), I'd be more likely to write the code like:
const getData = () => Promise.all([$.getJSON("getData.php"), $.getJSON("getOtherData.php")]);
getData()
.then(([data1, data2]) => { // note that data1, data2 are now the arguments to the function
let markerlist = [];
for (let i = 0; i< data1.length; i++) {
...
...
}
})
.catch(err => {
// handle errors here
});

Related

How to correctly pass parameters through nested functions in for loop?

I'm trying to use nodejs to run a loop functions oh_lawd_it_chonky() that is supposed to do the following: Loop target_users array passing each user to initialize() for authorization, for each given user get data using returnbigdatachunk() take that data and refine it or do something to it using process_returnbigdatachunk() push each of the results of that for each user to an array output.
I have each of the initialize(), returnbigdatachunk() and process_returnbigdatachunk() working individually and so I've omitted the details of those functions for the sake of simplicity in this post, I just can't seem to get the darn oh_lawd_it_chonky() function working. I don't quite understand how to pass through the user parameters correctly in nested functions like this. Please help if you can! Sorry this isn't quite a working example.
const target_users = ['user1', 'user2', 'user3'];
//authorize each user
function initialize(user) {
//do the stuff to get authorization
}
//loop through all the users and build an array of all the process_returnbigdatachunk
const oh_lawd_it_chonky = async () => {
for (var f = 0; f < target_users; f++) {
try {
//get data of passed in user
const returnbigdatachunk = async () => {
const auth = initialize(user[target_users[f]]);
let output = [];
//refine data of passed in user
const process_returnbigdatachunk = async () => {
try {
const data = await returnbigdatachunk();
//push refined data into output array
output.push(process_returnbigdatachunk)
console.log("crampus: " + output)
} catch (e) {
console.error('Failed to process returnbigdatachunk', e);
}
};
};
} catch (e) {
console.error('Failed to process returnbigdatachunk', e);
}
}
};
setTimeout in for-loop does not print consecutive values
block-scoped
change you var f = 0 to let f = 0

How to do sequencial HTTP calls?

I have a couple of APIs I need to call to collect and merge information.
I make the first API call and, based on the result, I make several calls to the second one (in a loop).
Since http requests are asynchronous I'm loosing the information. By the time the second step is finished the server (nodejs) already sent the response back to the client.
I've already tried to, somehow, use the callback functions. This managed to keep the response to the client waiting but the information of the second call was still lost. I guess somehow the variables are not being synchronized.
I also did a quick test with away/async but my Javascript mojo was not enough to make it run without errors.
/* pseudo code */
function getData(var1, callback){
url= "http://test.server/bla?param="+var1;
request.get(url, function (error, response, body){
var results = [];
for(var item of JSON.parse(body).entity.resultArray) {
var o = {};
o['data1'] = item.data1;
o['data2'] = item.data2;
o['data3'] = item.data3;
getSecondStep(o, function(secondStepData){
//console.log("Callback object");
//console.log(o);
o['secondStepData'] = secondStepData;
});
results.push(o);
}
callback(results);
});
}
function getSecondStep(object, callback){
url = "http://othertest.server/foobar?param=" + object.data1;
request.get(url, function (error, response, body){
var results = [];
if(response.statusCode == 200){
for(var item of JSON.parse(body).object.array) {
var o = {}
o['data4'] = item.data4;
o['data5'] = item.data5;
results.push(o);
}
callback(results);
}
});
}
What I would like is to be able to collect all the information into one JSON object to return it back to the client.
The client will then be responsible for rendering it in a nice way.
I recommend using the async / await pattern with the request-promise-native library.
This makes API calls really easy to make and the code is cleaner when using this pattern.
In the example below I'm just calling a httpbin API to generate a UUID but the principle applies for any API.
const rp = require('request-promise-native');
async function callAPIs() {
let firstAPIResponse = await rp("https://httpbin.org/uuid", { json: true });
console.log("First API response: ", firstAPIResponse);
// Call several times, we can switch on the first API response if we like.
const callCount = 3;
let promiseList = [...Array(callCount).keys()].map(() => rp("https://httpbin.org/uuid", { json: true }));
let secondAPIResponses = await Promise.all(promiseList);
return { firstAPIResponse: firstAPIResponse, secondAPIResponses: secondAPIResponses };
}
async function testAPIs() {
let combinedResponse = await callAPIs();
console.log("Combined response: " , combinedResponse);
}
testAPIs();
In this simple example we get a combined response like so:
{
{
firstAPIResponse: { uuid: '640858f8-2e69-4c2b-8f2e-da8c68795f21' },
secondAPIResponses: [
{ uuid: '202f9618-f646-49a2-8d30-4fe153e3c78a' },
{ uuid: '381b57db-2b7f-424a-9899-7e2f543867a8' },
{ uuid: '50facc6e-1d7c-41c6-aa0e-095915ae3070' }
]
}
}
I suggest you go over to a library that supports promises (eg: https://github.com/request/request-promise) as the code becomes much easier to deal with than the callback method.
Your code would look something like:
function getData(var1){
var url = "http://test.server/bla?param="+var1;
return request.get(url).then(result1 => {
var arr = JSON.parse(body).entity.resultArray;
return Promise.all( arr.map(x => request.get("http://othertest.server/foobar?param=" + result1.data1)))
.then(result2 => {
return {
data1: result1.data1,
data2: result1.data2,
data3: result1.data3,
secondStepData: result2.map(x => ({data4:x.data4, data5:x.data5}))
}
})
});
}
And usage would be
getData("SomeVar1").then(result => ... );
The problem is that you are calling the callback while you still have async calls going on. Several approaches are possible, such us using async/await, or reverting to Promises (which I would probably do in your case).
Or you can, well, call the callback only when you have all the information available. Pseudo code follows:
function getData(var1, callback){
url= "http://test.server/bla?param="+var1;
request.get(url, function (error, response, body){
var results = [];
var items = JSON.parse(body).entity.resultArray;
var done = 0, max = items.length;
for(var item of items) {
var o = {};
o['data1'] = item.data1;
o['data2'] = item.data2;
o['data3'] = item.data3;
getSecondStep(o, function(secondStepData){
//console.log("Callback object");
//console.log(o);
o['secondStepData'] = secondStepData;
results.push(o);
done += 1;
if(done === max) callback(results);
});
}
});
}
(note that since this is pseudo code, I am not checking for errors or handling a possible empty result from request.get(...))
You need to call the callback of first function only when all the second callback functions have been called. Try this changes:
function getData(var1, callback) {
url = "http://test.server/bla?param=" + var1;
request.get(url, function (error, response, body) {
var results = [],count=0;
var arr = JSON.parse(body).entity.resultArray;
for (let [index, value] of arr.entries()) {
var o = {};
o['data1'] = item.data1;
o['data2'] = item.data2;
o['data3'] = item.data3;
getSecondStep(o, function (secondStepData) {
//console.log("Callback object");
//console.log(o);
o['secondStepData'] = secondStepData;
results[index] = o;
count++;
if (count === arr.length) {
callback(results);
}
});
}
});
}

Call a class method after another has finished its execution

I'm trying to execute a class method after another has finished. The first one calls an API and fills an array and a dictionary. Then, the next method takes the data and creates a new dictionary. I cannot make it work sequentially, any idea?
I have tried to add callback when calling the first method, but it ain't working.
this.fillListDictionary(function(){
this.fillCatDictionary();
});
fillListDictionary(callback) {
this._categoryList = [];
this._propertyDictionary = [];
//Fill list and dictionary calling the API ()
callback();
}
fillCatDictionary() {
this._catDictionary = [];
this._categoryList.forEach((category) => {
this._propertyDictionary.forEach((property) => {
if(property.properties.includes(category)){
var f_index = this._catDictionary.findIndex(x => x.category == category);
if(f_index >= 0){
this._catDictionary[f_index].dbIds.push(property.dbId);
}
else {
var cat = new Object();
cat.category = category;
cat.dbIds = [property.dbId];
this._catDictionary.push(cat);
}
}
})
})
}
I'd like to make it work sequentially: fillCatDictionary has to execute after fillListDictionary has done its job.

issue with pushing data into a new array while in a promise chain

I'm having trouble figuring out why my data is not being push into my new array, "results". newArr[0].mscd.g[i] is a list of several objects.
var axios = require('axios');
var moment = require('moment');
var _ = require('lodash');
var getData = function() {
return getNBASchedule().then(function(payload) {
return filterByMonth('January', payload);
}).then(function(result) {
return result
});
}
....
getData grabs the data from baseURL and returns a list of objects.
var getMonthlySchedule = function(data){
var results = [];
var newArr = data.slice(0, data.length);
for (var i = 0; i <= newArr[0].mscd.g.length; i++) {
if (newArr[0].mscd.g[i].v.tid === 1610612744 || newArr[0].mscd.g[i].h.tid === 1610612744) {
results.push(newArr[0].mscd.g[i]); <---- //does not seem to work
// however if I were to console.log(newArr[0].mscd.g[i],
// I would see the list of objects)
}
}
return results; <-- //when i console at this point here, it is blank
};
var getSchedule = function () {
return getData().then(function(pl) {
return getMonthlySchedule(pl)
})
};
var monthlyResults = function() {
return getSchedule().then(function(r) {
console.log("result", r)
return r
});
};
monthlyResults();
You don't know when getSchedule() is done unless you use a .then() handler on it.
getSchedule().then(function(data) {
// in here results are valid
});
// here results are not yet valid
You are probably trying to look at your higher scoped results BEFORE the async operation has finished. You HAVE to use .then() so you know when the operation is done and the data is valid.
Your code should simplify as follows :
var getData = function() {
return getNBASchedule().then(function(payload) {
return filterByMonth('January', payload);
});
}
var getMonthlySchedule = function(data) {
return data[0].mscd.g.filter(function(item) {
return item.v.tid === 1610612744 || item.h.tid === 1610612744;
});
};
var monthlyResults = function() {
return getData()
.then(getMonthlySchedule)
.then(function(r) {
console.log('result', r);
return r;
});
};
monthlyResults();
This may fix the problem. If not, then :
Check the filter test. Maybe those .tid properties are String, not Number?
Check that data[0].mscd.g is the right thing to filter.

Callback functions for syncing in javascript

I am trying to have a function in Node.js with Mongoose that is a .save function for an HTTP request and I am trying to extract the geocoordinates and save them as an array in the Mongoose Schema in MongoDB. However, it seems that I am having a syncing issue because the coordinates are printed as undefined at first and I have to refresh the page to have them displayed. I thought callbacks would solve this, but they do not. (I've added the related code snippets below.) Am I doing something wrong with the callbacks or should I do something else? Thanks in advance!
ArticleProvider.prototype.save = function(articles, callback) {
for( var i =0;i< parties.length;i++ ) {
article = articles[i];
article._id = articleCounter++;
article.created_at = new Date();
if (article.coords === undefined){
geocode(article.address, function(results){
article.coords = results;
});
}
callback(null, articles);
};
var geocoder = require('Geocoder');
function geocode(address, callback) {
geocoder.geocode( address, function( err , data) {
// console.log(data.results[0].geometry.location);
//console.log( [data.results[0].geometry.location.lng, data.results[0].geometry.location.lat]);
var coords = [data.results[0].geometry.location.lng, data.results[0].geometry.location.lat];
console.log(coords);
callback(coords);
});
}
You're calling the callback callback(null, articles); before geocode calls its callback. You need to make sure all of those finish before you call the callback. I would normally recommend an async library such as Caolan's async (look at async.forEach), but for a single case of it that might be overkill. I would suggest:
ArticleProvider.prototype.save = function(articles, callback) {
var finishedCount = 0;
var finishedOne = function() {
finishedCount++;
if(finishedCount == parties.length)
callback(null, articles);
};
for( var i =0;i< parties.length;i++ ) {
article = articles[i];
article._id = articleCounter++;
article.created_at = new Date();
if (article.coords === undefined){
geocode(article.address, function(results){
article.coords = results;
finishedOne();
});
}
else {
finishedOne();
}
}
};
I also fixed your bracket mismatch, which I assume is a copy/paste error.

Categories

Resources