Why I am getting array[i] value of for loop is undefined - node.js

I am new to nodeJs. In for loop I am getting array[i] value undefined after get the data from some url. I don't know how to solve this problem. Please help me.
Here is my code
urls =['url1','url2','url3','url4']
for (var i = 0; i < urls.length; i++) {
console.log(urls[i]) // getting value
var opt = {
url :urls[i],
headers: {
'Authorization': 'Bearer '+reply
}
}
request.get(opt,function(error,response,body) {
console.log(urls[i]) // getting undefined
if(!error) {
enrichment_data.push({type:body});
} else {
enrichment_data.push({type:''})
}
})
}

According to MDN, this type of logical error in understanding JavaScript closures, is a common mistake.
There are two practical ways to solve this. Sticking with ES5 only, the most canonical way to solve this would be to use urls.forEach() instead:
var urls = ['url1', 'url2', 'url3', 'url4']
urls.forEach(function(url, i) {
console.log(urls[i]) // getting value
var opt = {
url: urls[i],
headers: {
'Authorization': 'Bearer ' + reply
}
}
request.get(opt, function(error, response, body) {
console.log(urls[i]) // getting undefined
if (!error) {
enrichment_data.push({
type: body
});
} else {
enrichment_data.push({
type: ''
})
}
})
})
Using this method, you create a new function scope that preserves the value of each i, even within the asynchronous function, so that the variable reference doesn't get overwritten by later iterations of the for loop you had earlier.
Your second option would be to introduce ES6 lexical scope variable declarations using let like so:
var urls = ['url1', 'url2', 'url3', 'url4']
for (let i = 0; i < urls.length; i++) {
console.log(urls[i]) // getting value
var opt = {
url: urls[i],
headers: {
'Authorization': 'Bearer ' + reply
}
}
request.get(opt, function(error, response, body) {
console.log(urls[i]) // getting undefined
if (!error) {
enrichment_data.push({
type: body
});
} else {
enrichment_data.push({
type: ''
})
}
})
})
Lexical scope means that any reference to i inside the block scope of the for loop, even within the body of the asynchronous function, will reference that particular iteration of the for loop, allowing it to behave as it appears like it should.

Great answer by Patrick. I wanted to add few points.
To answer the question, why you are getting undefined at console.log(urls[i]) because the value of i is 4 and nothing is present at index 4.
Now to the point why i is 4. request is an async call and once you call request.get, it goes through event loop and gets written to the stack (with the right url) to be called. . This stack would be called only when the current call is over, which is after your for loop is over. Your loops gets over only when i is 4 and var is global scope
Here is a great video to understand how event loop works.

Related

How can I get the original request URL in the response callback?

I have a loop like this:
var req;
for (var i=0; i<sites.length; i++) {
req = https.get(sites[i], handleRequest);
req.on('error', handleError);
}
The callback (handleRequest) runs asynchronously, for each website being requested.
However, the only parameter in handleRequest seems to be a "response".
When the callback is run, the loop has already completed, so how can I keep track of which website is this response for, so I can handle it accordingly?
You can change your handleRequest to take two parameters - url being the first of them. With that you can partially apply the function via Function#bind and so set the url parameter at the time of calling but you'll still wait for the second argument.
let sites = [
"https://example.com",
"https://example.net",
"https://example.org"
];
function handleRequest(url, res) {
console.log("handling:", url);
/* handling code */
}
//minimalistic dummy HTTP module that responds after 1 second
let https = {
get: handler => setTimeout(handler, 1000)
}
for (var i=0; i < sites.length; i++) {
let url = sites[i];
https.get(handleRequest.bind(this, url)) //partially apply handleRequest
}
You can get a similar result via currying - instead of having two parameters, first take one, then return a function that takes the other. It leads to (in my opinion) better syntax when calling:
let sites = [
"https://example.com",
"https://example.net",
"https://example.org"
];
function handleRequest(url) {
return function actualHandler(res) {
console.log("handling:", url);
/* handling code */
}
}
//minimalistic dummy HTTP module that responds after 1 second
let https = {
get: handler => setTimeout(handler, 1000)
}
for (var i=0; i < sites.length; i++) {
let url = sites[i];
https.get(handleRequest(url))
}

Promise issue with nested loops

I'm trying to write a server in node, and I am taking data from a SQL database, I have another function that does that, but inside this function I have that list of data (thisData). In my server I need to query another server (offsite) to get a status update, update objects in the list based on this, and then respond with the JSON data.
I've been working on this for a long time and tried to find solutions based on other answers on here and I've yet to find something. I was originally trying to do this with just callbacks, but now I'm trying to do it with Promise.all()
I have my object thisData, it is a list of three "areas" which each contain a list of objects.
Both of these code snippets are inside a standard listener function for the standard http lib:
var server = http.createServer((request, response => {...
Immediately inside this function I also have a call to get the data:
getData(function(Data){
thisData = JSON.parse(data);
all the code snippets are inside this getData which is inside my listener function.
I also have a function that returns a promise to do the async web query:
var statusRequest = function(myArea, obj){
var url = 'https://www.example.com/data/' + obj.id + '/mode.json';
var options = {
method: 'GET',
uri: url,
strictSSL: false,
headers: {
'Authorization': config.auth_key,
'Accept': "application/json",
}
}
return new Promise((resolve, reject) => {
request(options, function (err, body, resp) {
var status = JSON.parse(resp).mode;
if (status == "success") thisData[area][obj.col].status = 0;
else if (status == "fail") thisData[area][obj.col].status = 1;
else thisData[area][obj.col].status = -1;
resolve(thisData[area][obj.col].status);
});
});
}
Then I have a loop that traverses the areas and objects, and I add each promise to a list and call Promise.all(), then I respond with the updated list:
var promises = [];
Object.keys(thisData).forEach(function(area){
for (var col = 0; col < thisData[area].length; col++){
promises.push(statusRequest(area, thisData[area][col]));
}
});
Promise.all(promises).then( (val) => {
console.log(val);
response.end(JSON.stringify(thisData));
});
I was initially having problems with all the promises resolving when col == thisData[area].length, and so I passed the actual object (which contains its own column information) into the statusRequest function so it shouldn't matter if the col variable changes.
The problem I'm having is that my console prints the correct status, but my JSON response does not contain the correct status, in fact it seems as though none of them are updating.
I would really appreciate any help, thanks in advance!

NodeJs item variable in array only takes the first value in a for loop

I am using expressJs to route some POST requests.
From the client side I pass an object of objects and in the server I iterate over each of them with a for loop.
My problem, the variable cantidad in the loop only takes the first value instead of being refreshed into the pool.query, but before the pool.query it takes the right value.
So, the line below is ok.
console.log("cantidad before query: " + cantidad);
But the line below is bad. It has the first value.
console.log("cantidad in query: " + cantidad);
This is part of my code.
for (var key in objects) {
if (objects.hasOwnProperty(key)) {
...
console.log("cantidad before query: " + cantidad);
pool.query(qProducto,idProducto, function (error, results, fields {
if (error) {
...
} else {
console.log("cantidad in query: " + cantidad);
...
This is the full POST in ExpressJs.
app.post("/commanda", function (req, res) {
var idCuenta = req.body.idCuenta;
var idEmpleado = req.body.idEmpleado;
var fechaRegistro = req.body.fechaRegistro;
var cuenta_mesero = "C:" + idCuenta + ":E:" + idEmpleado;
var objects = req.body.objects;
var element = {};
for (var key in objects) {
if (objects.hasOwnProperty(key)) {
var qProducto = "SELECT descripcionProducto FROM PRODUCTO WHERE idProducto = ? ;";
var descProducto = '';
console.log("cantidad in commanda2 : " + objects[key].cantidad );
try {
pool.query(qProducto, objects[key].idProducto, function (error, results, fields) {
if (error) {
console.error(error);
console.error("Failed with query: " + qProducto);
res.status(500).end();
throw error;
} else {
console.log("cantidad in commanda4 : " + objects[key].cantidad );
descProducto = JSON.stringify(results[0].descripcionProducto);
element = {
idProducto:objects[key].idProducto,
cantidad:objects[key].cantidad,
descProducto:descProducto,
cuenta_mesero:cuenta_mesero,
fechaRegistro:fechaRegistro
};
imprimirOrden(element);
}
});
} catch (error) {
callback(error);
}
}
}
printer.printVerticalTab();
res.status(200).end();
});
This is how an object looks like.
{ '0':
{ idProducto: '28',
cantidad: '3',
descProducto: 'Product1',
precioProducto: '3500',
precioTotal: 10500,
'$$hashKey': 'object:345' },
'1':
{ idProducto: '29',
cantidad: '2',
descProducto: 'Product2',
precioProducto: '4500',
precioTotal: 9000,
'$$hashKey': 'object:346' } }
This happens because the function for is synchronous but the function poll.query is asynchronous. What this means is that using the for loop you are essentially queuing some queries. You are not executing them one by one. So the for loop will finish before even one result is returned from the query. If you want to use data from the query for the next iteration you should start using async.js, an npm module that helps you avoid this problems. TL;DR the console log that you think runs in query is actually run before even one query has finished. More information is needed on where you declare the variable cantidad and when you change it to accurately understand the problem.
UPDATE:
What I told you at first was quite wrong because of the fact that I misunderstood the in-detention of the else {}. But what I told you already is actually the problem. It was well obfuscated.The for loop finishes before even one query has finished. They are just queued. So the second console.log will have the key of the last key in the loop. If you need logic that requires knowing in which iteration you are you should implement an async function in order to know in which iteration you actually are. If you don't want to use the async library you can use something like this.
First add this function in the bottom of your js file
https://pastebin.com/4tR0xaTY
You essentially created an async for loop that you can now know in which iteration you are using loop.iteration(). Then replace your post code with the code written below ( To include the async loop ).
https://pastebin.com/YzZU7bqp

Getting original request object during multiple asynchronous calls in nodejs-request

I have multiple HTTP requests in a nodejs app that each returns a word of a sentence. The replies will come at different times, so I'm saving them in a dictionary, with the key being the original sentence's word index. Problem is, when I access the request object, I only get the last one.
var completed_requests = 0;
sentence = req.query.sentence;
sentence = "sentence to be translated"
responses=[];
words = sentence.split(" ");
for(j=0;j<words.length;j++){
var word = words[j];
var data={
word:word
};
var options = {
url: 'example.com',
form:data,
index:j
};
request.post(options, function(err,httpResponse,body){
options = options;
if(!err){
responses.push({options.index: body});
completed_requests+=1;
if(completed_requests==words.length){
var a="";
for(var k=0;k<words.length;k++){
a+=responses[k]+" ";
}
res.render('pages/index', { something: a });
}
}
else{
//err
}
});
}
Basically, when I access the object.index object, the object returned isn't the one used for the original request, but the last one (for some reason). How should I resolve this?
When we take a look at how the code is evaluated by JavaScript due to it's async nature in node.js the problem becomes obvious:
For the first word the loop for(j=0;j<words.length;j++){ is executed.
The value of j is assigned to options.index. For the loop run this options.index has now the value 0.
request.post(options, function(err,httpResponse,body){ is executed but the callback handler will be invoked later.
For the first word the loop for(j=0;j<words.length;j++){ is executed.
The value of j is assigned to options.index. options.index has now the value 1.
request.post(options, function(err,httpResponse,body){ is executed but the callback handler will be invoked later.
The problem becomes obvious now since no new options objects are created but the value of j is assigned to options.index in every loop run. When the first callback handler is invoked options.index has the value words.length - 1.
To fix the problem we will wrap creating the options object in a function executeRequest
var completed_requests = 0;
sentence = req.query.sentence;
sentence = "sentence to be translated"
responses=[];
words = sentence.split(" ");
for(j=0;j<words.length;j++){
var word = words[j];
var data={
word:word
};
function executeRequest(url, form, index) {
var options = {
url: url,
form: form,
index: index
};
request.post(options, function(err,httpResponse,body){
// options = options; Superfluous
if(!err){
responses.push({ [index]: body});
completed_requests+=1;
if(completed_requests==words.length){
var a="";
for(var k=0;k<words.length;k++){
a+=responses[k]+" ";
}
res.render('pages/index', { something: a });
}
}
else{
//err
}
});
}
executeRequest('example.com', data, j);
}
A good read about scoping and hoisting in JavaScript can be found here http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html
You need to use an async routine such as forEach or map, also I suggest you read up on the async nature of node to help understand how to handle callbacks for io.

Javascript wait for request to process in a for loop

In my chrome extension i was checking for a function which can stop my for loop from processing till it gets a response from content scripts. Sharing the sample code below
function abc() {
chrome.tabs.query({'status': 'complete'}, function(tabArray) {
for (var i = 0, tab; tab = tabArray[i]; i++) {
var currentUrl = tab.url;
var tabId = tab.id;
if (currentUrl.match(otherthing)) {
chrome.tabs.sendRequest(tabId, {'type': 'getrequiredthing'},
function(response) {
if (response.isrequiredthind) {
callfunction(tabId);
}
}
);
}
}
});
}
Here when i get the matching url in else if i'm sending a request to the page for getting some info, if my info is positive i need to callfunction. But here in the for loop tabId is iterating very fastly and even if the response is positive it is calling the callfunction with next(or next) tabId.
Can you give your opinions on solving this keep waiting the for loop this response is received.
Thanks
The problem is that the third argument to sendRequest does not block on the request being ready. By design, JavaScript almost never blocks. This is a Good Thing. Instead, it uses an "event-driven" model.
Another problem is due to lexical scoping: When callfunction is called, tabId has the most recent value, not the value when sendRequest was called. To get around this, you need to create a separate scope for each loop iteration e.g.
for (...) {
var tabId = ...;
if (...) {
(function (localTabId) {
chrome.tabs.SendRequest(..., function (response) {
if (response.isrequiredthind) {
callfunction(localTabId);
}
}
})(tabId);
}
}

Resources