Async Javascript for loop - javascript

I have a problem with my for loop only one if block works.
I have to get the temperature from a whether API and put it back my table using my put method for every city.
The problem is that I can't fill all the cities.
How can i use ASYNC (https://caolan.github.io/async) module to make for loop work?
Here is my code :
for (i in guests) {
ville = i[ville_id];
guest_id = i._id;
if (ville) {
results.push(req2 = http.get('http://api.openweathermap.org/data/2.5/weather?q=' + ville + ',france&APPID=xxx', function(res) {
return res.on('data', function(chunk) {
var options, test, url;
test = chunk.toString();
obj = JSON.parse(test);
res = obj.main.temp;
i[temp_id] = res;
url = update_guest + guest_id.toString();
options = {
method: 'PUT',
url: update_guest + guest_id,
qs: {
api_key: 'xxx'
},
headers: {
'Cache-Control': 'no-cache',
'Content-Type': 'application/json'
},
body: {
'595f9b2a5ea9cb0004c21290': res
},
json: true
};
return request(options, function(error, response, body) {
if (error) {
throw new Error(error);
}
return console.log(body);
});
});
}));
}
}

This is a pretty common issue in JavaScript due to the way closures work. What is happening is your loop for (i in guests) is completed before any of your callbacks (function(res){...}) have a chance to run. Your callbacks reference variable i, but they don't "remember" what the value of i was when you made your HTTP request. Instead, when your callbacks execute, i will be whatever the last value in guests is since your for...in loop would have already completed by the time your callbacks are executed.
For example, let's say guests has two keys in it, guest1 and guest2. Here's what would happen:
The first iteration of your loop runs. i is now guest1. The first HTTP request is fired.
The second iteration of your loop runs. i is now guest2. The second HTTP request is fired.
Both HTTP requests complete and your callbacks are executed, but i is set to guest2. As such, both callbacks references to i will be guest2.
What you need to do is find a way to give your callbacks access to the value of i at the time your request was fired. You could do this by creating your callbacks with a function that takes i as a parameter like so:
for (i in guests) {
ville = i[ville_id];
guest_id = i._id;
if (ville) {
results.push(req2 = http.get('http://api.openweathermap.org/data/2.5/weather?q=' + ville + ',france&APPID=xxx', (function (localI) { return function(res) {
return res.on('data', function(chunk) {
// removing code for clarity
localI[temp_id] = res;
// removing code for clarity
return request(options, function(error, response, body) {
// removing code for clarity
});
});
})(i)));
}
}
You can read more about this approach here.
What I've done is created a function which returns your callback and takes a parameter localI. I then immediately passed i as that parameter, returning a callback with localI set to the current value of i. Then I changed your reference to i inside the callback to localI. Note that your references to ville and guest_id inside your callback will also have the same issue as i. You'll either need to pass those as parameters as well or declare them inside your callback.

Related

Callback is not a function for update in Node JS

begginer in Javascript and Node Js here.
While trying to do my first, simple update function, i got the error :
TypeError: callback is not a function.
I searched for the answer online but this problem is still a mistery.
function UpdateProductsCodes(columns, returnColumns, type, callback) {
for (var i = 0; i < columns.ids.length; i++) {
updateSql = "UPDATE TProductCodes SET code =?, product_id =? OUTPUT inserted.id, inserted.code, inserted.product_id INTO #returnValues WHERE ids =?";
var params = [];
params.push(columns.codes[i]);
params.push(columns.product_ids[i]);
params.push(columns.ids[i]);
sql.query(conn_str, updateSql, params, function (err, products, more) {
//Code stops here
//TypeError: callback is not a function
if (err) {
callback(err, null);
return;
};
if (!more) {
callback(null, products);
}
});
}
}
This function should do a simple update, nothing more. Its used here:
UpdateProductsCodes(req.body.entities, conditions, returnColumns, type, function (err, products) {
if (err) {
console.dir(err);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.write(JSON.stringify(utils.GenerateResponse(err.message, true, 'JSON')));
res.end();
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify(utils.GenerateResponse(products, false, type)));
res.end();
});
The problem is that you are simply sending the wrong number of arguments when you call the function.
The function accepts four inputs: columns, returnColumns, type, callback. But in your example, you are sending five inputs: req.body.entities, conditions, returnColumns, type, function (err, products)
The last one (the function, in this case) is therefore ignored. The value which the function is receiving as being the callback value is in fact the one you've named type when you call the function, because that's the fourth argument you provide. This value is not an executable function - which is what the error message is telling you.
Now I don't know which values are the ones you actually need/want to send to the function, but clearly one of them is redundant and you need to remove it from the calling code. Based purely on the names, I'd guess that one of either req.body.entities or conditions is not needed, but of course I can't see what those variables contain, and I can't be certain of your intent, so you'll have to work it out yourself.
P.S. I also note that your function never actually uses the returnColumns or type parameters which it receives, so you maybe should consider whether you actually need to accept these at all. Perhaps they can be removed.

sails.js node.js Parse JSON on controller

In my controller called MapController I'm doing a function to do a parse of remote json files, and from an if-else structure add some values in an array called "parsewebservice", apparently everything is working fine but console.log ( parsewebservice); is not returning the values that were passed to the array "parsewebservice" in the place where it is returning it empty. But when I put it inside the forEach it returns, but everything cluttered and repeated then is not the right way.
I wanted to know why the values that were passed to the array "parsewebservice" are not going along with the variable after populada and what would be the correct way to do it?
Here is my code below:
/**
* MapController
*
* #description :: Server-side logic for managing Maps
* #help :: See http://sailsjs.org/#!/documentation/concepts/Controllers
*/
module.exports = {
index: function(req, res, next) {
Data.find(function foundData(err, datas) {
if (err) return next(err);
var parsewebservice = [];
datas.forEach(function(data, index) {
var req = require("request");
var url = data.address + "?f=pjson";
req(url, function(err, res, retorno) {
if (err) {
console.log(err);
} else {
var camadas = JSON.parse(retorno);
if (camadas.mapName) {
camadas.layers.forEach(function(campo, i) {
if (campo.subLayerIds != null) {
} else if (campo.subLayerIds == null) {
parsewebservice.push([i, "dynamicMapLayer", campo.name, data.address]);
}
});
} else if (camadas.serviceDataType) {
parsewebservice.push([null, "imageMapLayer", camadas.name, data.address]);
} else if (camadas.type) {
parsewebservice.push([null, "featureLayer", camadas.name, data.address]);
}
}
});
});
console.log(parsewebservice);
});
},
};
My first comment has to be that you should not combine function(req, res) with var req = require('request')... you lose your access to the original req object!
So, you need to run a list of async tasks, and do something when they are all complete. That will never be entirely easy, and no matter what, you will have to get used to the idea that your code does not run from top to bottom as you've written it. Your console.log at the bottom runs before any of the callbacks (functions you pass in) you pass to your external requests.
The right way to do this is to use promises. It looks like you are using this request library, whose returned requests can only accept callbacks, not be returned as promises. You can create your own promise wrapper for them, or use an alternative library (several are recommended on the page).
I don't want to write a whole intro-to-promises right here, so what I will do is give you a less pretty, but maybe more understandable way to run some code at the completion of all your requests.
Data.find(function foundData(err, datas) {
if (err) return next(err);
var parsewebservice = [];
// here we will write some code that we will run once per returned data
var processResponse = function(resp) {
parsewebservice.push(resp);
if(parsewebservice.length >= datas.length) {
// we are done, that was the final request
console.log(parsewebservice);
return res.send({data: parsewebservice)}); // or whatever
}
};
datas.forEach(function(data, index) {
var request = require("request");
var url = data.address + "?f=pjson";
request(url, function(err, res, retorno) {
// do some processing of retorno...
// call our function to handle the result
processResponse(retorno);
});
});
console.log(parsewebservice); // still an empty array here
});
I solved the problem.
the "request" module is asynchronous so we need to wait for it to respond and then send the response to the view.
To do this we created a function called "foo" to contain the foreach and the request, we made a callback of that function and finally we made the response (res.view) within that function, so that the controller response would only be sent after the response of the "foo" function to the callback. So we were able to parse.json the data from the "data" collection using foreach and the "request" module and send the objects to the view.
Many thanks to all who have helped me, my sincere thanks.

NodeJS Variable Collision? Async / Sync with Request

Issue : Data INFO_X sometimes and randomly become Null.
Question ,
Does the variable overwrites each other for INFO_1 INFO_2 INFO_3 since Nodejs run fast unlike PHP it follows a sequence/step by step.
I checked for NULLS before doing a request but the debug shows it's NOT NULL before executing the 2nd request, at random, random variables will become null upon submitting the 2nd request.
I also checked my source is definitely not returning any null.
Is the variable being overwritten before the 2nd request is sent or what? Please advice.
var request = require('request');
var urls = [ 'URL',
'URL',
'URL'];
urls.forEach(processUrl);
function processUrl(url) {
request(url, function (error, response, body) {
if (!error) {
var obj = JSON.parse(body);
for (var i = 0, len = obj['products'].length; i < len; ++i) {
var data = obj['products'][i];
var INFO_1 = data.INFO_1
var INFO_2 = data.INFO_2
var INFO_3 = data.INFO_3
request("URL/POSTINFO?INFO_1="+INFO_1+"&INFO_2="+INFO_2+"&INFO_3="+INFO_3+"&seller_id=", function(error, response, body) {
console.log(body);
});
}
}
});
}
Yes, that is the case, because request function is asynchronous. I wouldn't call nodejs "faster" than PHP, it just runs asynchronous request methods, while PHP is generally synchronous.
You could resolve the issue with promises, e.g. Promise.all([]) and provide an array of request functions (see here: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all), or in your case, use this library https://github.com/caolan/async#forEach
Since you're using callbacks in the request function, your best option is to use async as provided in the link above. Here's an example code:
function request(url, cb) {
setTimeout(() => {
cb(url + ' accessed at ' + new Date());
}, 2000);
}
var urls = ['URL1', 'URL2', 'URL3'];
async.each(urls, (item) => {
console.log(item);
request(item, (value) => {
request(value, (newValue)=>{
console.log(newValue);
});
});
}, (err) => {
console.log(err);
});
Here's a working example: https://plnkr.co/edit/Q5RAvKdaLxV9cUT4GP4w?p=preview

NODE - wait until one api call has completed before triggering the next api call

I am making a POST request in one of my routes add-users. I have created an array called success. Once the request has returned the response I want to trigger the next API call.
It is not working at the moment as I think I am sending too may API requests at once. I think the solution is that I wait until the first response has returned a response and finished, and then trigger the next API call.
Is this assumption correct? If so, can anyone advise how to implement this? I have tried to use .on('end'....
Please see my code below.
app.get('/add-users', function (req, res) {
var success = [];
var count = 0;
for(var i = 0; i < users.length; i++) {
var name = users[i].userId;
request({
url: url,
headers: {
'Authorization': 'Bearer ' + users[i].accessToken,
'Content-Type': 'application/json'
},
method: 'PUT',
json: true
}, function(err, resp, body){
if (!err && resp.statusCode === 200) {
success.push(name);
}
})
.on('end', function(){
console.log('this is the end');
count++;
if(count === users.length) {
res.json(success);
}
});
}
});
Classic! I've had this issue as well when I was first dealing with node.
Lemme explain by first copying some parts of your code:
app.get('/add-users', function (req, res) {
// [...]
for(/* user in users */) {
request(/* [...] */)
.on('end', function(){
console.log('this is the end');
count++;
if(count === users.length) {
res.json(success);
}
});
}
});
Node, in contrast to other languages (e.g. Ruby) does non-blocking I/O. This means that it performs almost all I/O operations (like making an HTTP request) asynchronously. Essentially, when you initiate a request, it will not wait for the response.
In your loop this means that it will fire all requests one after the other without waiting for their responses:
start loop
make request
make request
make request
end of loop
... a little later
handle response
handle response
handle response
I assume that what you want looks like this:
start loop
make request
handle response
make request
handle response
make request
handle response
end of loop
One trick I found to get around node's non-blocking nature and do sequential requests is to write a recursive function like this:
function getAllUsers(users) {
function getOneUser(users) {
let user = users.pop();
request(/* [...] */)
.on('end', function() {
console.log("done with ONE user");
if(users.length) { // do we still have users to make requests?
getOneUser(users); // recursion
} else {
console.log("done with ALL users");
res.json(success);
}
});
}
// make a copy of the original users Array because we're going to mutate it
getOneUser(Array.from(users));
}
What the above will do is make one request for one user, then when the response arrives fire another request.
I hope this helps.
Here is how I usually chain multiple requests together. Attempting to call an async function within a loop will not work the way you have written your code. Instead I would recommend calling the next request in the callback of your initial request.
//set index
var index = 0;
function callAPI(user) {
//increment index
index++
request({request:object},function(err, res){
if (index <= users.length) {
callAPI(users[index])
}
});
}
//call function with 0 index
callApi(users[0])
Also, if you console.log(name) in your code above you will see the issue for yourself.

Returning a value from callback function in Node.js [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
I am facing small trouble in returning a value from callback function in Node.js, I will try to explain my situation as easy as possible. Consider I have a snippet, which takes URL and hits that url and gives the output:
urllib.request(urlToCall, { wd: 'nodejs' }, function (err, data, response) {
var statusCode = response.statusCode;
finalData = getResponseJson(statusCode, data.toString());
});
I tried to wrap it inside a function and return a value like this:
function doCall(urlToCall) {
urllib.request(urlToCall, { wd: 'nodejs' }, function (err, data, response) {
var statusCode = response.statusCode;
finalData = getResponseJson(statusCode, data.toString());
return finalData;
});
}
Because in my Node.js code, I have a lot of if-else statement where value of urlToCall will be decided, like this:
if(//somecondition) {
urlToCall = //Url1;
} else if(//someother condition) {
urlToCall = //Url2;
} else {
urlToCall = //Url3;
}
The thing is all of the statements inside a urllib.request will remain same, except value of urlToCall. So definitely I need to put those common code inside a function. I tried the same but in doCall will always return me undefined. I tried like this:
response = doCall(urlToCall);
console.log(response) //Prints undefined
But if I print value inside doCall() it prints perfectly, but it will always return undefined. As per my research I came to know that we cannot return values from callback functions! (is it true)? If yes, can anyone advice me how to handle this situation, as I want to prevent duplicate code in every if-else blocks.
Its undefined because, console.log(response) runs before doCall(urlToCall); is finished. You have to pass in a callback function aswell, that runs when your request is done.
First, your function. Pass it a callback:
function doCall(urlToCall, callback) {
urllib.request(urlToCall, { wd: 'nodejs' }, function (err, data, response) {
var statusCode = response.statusCode;
finalData = getResponseJson(statusCode, data.toString());
return callback(finalData);
});
}
Now:
var urlToCall = "http://myUrlToCall";
doCall(urlToCall, function(response){
// Here you have access to your variable
console.log(response);
})
#Rodrigo, posted a good resource in the comments. Read about callbacks in node and how they work. Remember, it is asynchronous code.
I am facing small trouble in returning a value from callback function in Node.js
This is not a "small trouble", it is actually impossible to "return" a value in the traditional sense from an asynchronous function.
Since you cannot "return the value" you must call the function that will need the value once you have it. #display_name already answered your question, but I just wanted to point out that the return in doCall is not returning the value in the traditional way. You could write doCall as follow:
function doCall(urlToCall, callback) {
urllib.request(urlToCall, { wd: 'nodejs' }, function (err, data, response) {
var statusCode = response.statusCode;
finalData = getResponseJson(statusCode, data.toString());
// call the function that needs the value
callback(finalData);
// we are done
return;
});
}
Line callback(finalData); is what calls the function that needs the value that you got from the async function. But be aware that the return statement is used to indicate that the function ends here, but it does not mean that the value is returned to the caller (the caller already moved on.)
Example code for node.js - async function to sync function:
var deasync = require('deasync');
function syncFunc()
{
var ret = null;
asyncFunc(function(err, result){
ret = {err : err, result : result}
});
while((ret == null))
{
deasync.runLoopOnce();
}
return (ret.err || ret.result);
}
If what you want is to get your code working without modifying too much. You can try this solution which gets rid of callbacks and keeps the same code workflow:
Given that you are using Node.js, you can use co and co-request to achieve the same goal without callback concerns.
Basically, you can do something like this:
function doCall(urlToCall) {
return co(function *(){
var response = yield urllib.request(urlToCall, { wd: 'nodejs' }); // This is co-request.
var statusCode = response.statusCode;
finalData = getResponseJson(statusCode, data.toString());
return finalData;
});
}
Then,
var response = yield doCall(urlToCall); // "yield" garuantees the callback finished.
console.log(response) // The response will not be undefined anymore.
By doing this, we wait until the callback function finishes, then get the value from it. Somehow, it solves your problem.

Categories

Resources