I'm new to NodeJS and I was trying to figure out stuff but unfortunately I couldn't find information on it with my knowledge.
Basicly, I want to use a variable from a function inside another function wich is a child of the main function.
Here is my code :
http.get(url, function(res) {
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
var jsonResult = JSON.parse(body);
for (var i=0;i<5;i++)
{
gameId = jsonResult.gameList[i].gameId;
url = 'http://somesite.com/' + gameId + '/0/token';
http.get(url, function(res) {
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
jsonRes = JSON.parse(body);
switch(i)
{
case 0:
var elo0 = jsonRes.interestScore;
module.exports.elo0 = elo0;
break;
case 1:
var elo1 = jsonRes.interestScore;
module.exports.elo1 = elo1;
break;
case 2:
var elo2 = jsonRes.interestScore;
module.exports.elo2 = elo2;
break;
case 3:
var elo3 = jsonRes.interestScore;
module.exports.elo3 = elo3;
break;
case 4:
var elo4 = jsonRes.interestScore;
module.exports.elo4 = elo4;
break;
}
});
}).on('error', function(e) {
console.log("Got error: ", e);
});
}
});
}).on('error', function(e) {
console.log("Got error: ", e);
});
Note that I didn't include everything, just the problematic part.
I want to use the variable i from the loop inside the switch but it doesn't work.
The problem here is that you are referencing i in your switch statement within an asynchronous callback. When you do this, you won't get the value of i at the time the function is created, but the final value of i at the end of the loop iteration.
There are a couple of ways to fix this - both involve trapping the current loop value of i in a closure for later reference by the callback.
for example:
for (var i=0;i<5;i++)
{
(function(idx) {
gameId = jsonResult.gameList[idx].gameId;
url = 'http://somesite.com/' + gameId + '/0/token';
http.get(url, function(res) {
...
res.on('end', function() {
jsonRes = JSON.parse(body);
switch(idx)
{
case 0:
break;
...
}
});
...
});
})(i);
}
Here, an anonymous function is created for each pass through the loop and called immediately passing the current value of the loop counter i as the incoming parameter idx.
Another approach (as mentioned in the comments above) is to refactor the inner part of your loop into a separate function and call it passing all necessary context:
function scoreHandler(jsonResult, idx) {
var gameId = jsonResult.gameList[idx].gameId;
var url = 'http://somesite.com/' + gameId + '/0/token';
http.get(url, function(res) {
...
res.on('end', function() {
jsonRes = JSON.parse(body);
switch (idx) {
...
}
});
})
.on('error', function(e) {
console.log("Got error: ", e);
});
}
Your refactored loop would look something like:
res.on('end', function() {
var jsonResult = JSON.parse(body);
for (var i = 0; i < 5; i++) {
scoreHandler(jsonResult, i);
}
});
Related
Can someone explain why callback function after loadData(symbol) is not being called? I have this function:
function plotChart(symbol) {
alert("symbol: " + symbol);
var data = loadData(symbol, function(){
console.log("data: ",data);
});
}
I'm expecting log.console to execute after loadData completes, but nothing gets output.
In case it matters, here is the loadData function.
function loadData(symbol) {
count = 0;
quotes = [];
$.getJSON("http://localhost:8080/springboot-crud-rest/api/v1/quotes-between?symbol=IBM&startDate=2020-01-01&endDate=2020-09-30", function(data) {
data.forEach(function(item){
var quote = {};
quote.date = item.id.date.substring(0,10);
quote.open = item.open;
quote.high = item.high;
quote.low = item.low;
quote.close = item.close;
quote.volume = item.volume;
quotes.push(quote); //put quote in array
});
console.log("quotes: ",quotes);
return quotes;
});
}
Here the console.log successfully prints out the quotes correctly.
EDIT:
Per #Aluan, I changed the code to look like this:
function plotChart(symbol) {
loadData(symbol).then(function(data) {
console.log("data: ", data);
});
}
function loadData(symbol) {
quotes = [];
$.getJSON("http://localhost:8080/springboot-crud-rest/api/v1/quotes-between?symbol=" +symbol +"&startDate=2020-01-01&endDate=2020-09-30")
.then(function (data) {
const quotes = data.map(function(item) {
return {
date: item.id.date.substring(0,10),
open: item.open,
high: item.high,
low : item.low,
close: item.close,
volume: item.volume
};
});
console.log("quotes: ",quotes);
return quotes;
});
}
But now I'm getting error:
Uncaught TypeError: Cannot read property 'then' of undefined
at plotChart (moneymachine.html:120)
at HTMLTableRowElement.
Line 120 is loadData(symbol).then(function(data) {
The definition of loadData is essential to understanding the reason.
Given
function loadData(symbol) {
count = 0;
quotes = [];
$.getJSON("http://localhost:8080/springboot-crud-rest/api/v1/quotes-between?symbol=IBM&startDate=2020-01-01&endDate=2020-09-30", function (data) {
data.forEach(function (item) {
var quote = {};
quote.date = item.id.date.substring(0, 10);
quote.open = item.open;
quote.high = item.high;
quote.low = item.low;
quote.close = item.close;
quote.volume = item.volume;
quotes.push(quote); //put quote in array
});
console.log("quotes: ", quotes);
return quotes;
});
}
We can see that it defines one parameter, symbol. You may pass additional arguments when calling it, but it clearly does not use them.
When you write
var data = loadData(symbol, function () {
console.log("data: ", data);
});
The second argument is simply ignored.
Here's an example of loadData that would accept and make use of a second argument that's a callback.
function loadData(symbol, callback) {
count = 0;
quotes = [];
$.getJSON("http://localhost:8080/springboot-crud-rest/api/v1/quotes-between?symbol=IBM&startDate=2020-01-01&endDate=2020-09-30", function(data) {
data.forEach(function(item){
var quote = {};
quote.date = item.id.date.substring(0, 10);
quote.open = item.open;
quote.high = item.high;
quote.low = item.low;
quote.close = item.close;
quote.volume = item.volume;
quotes.push(quote); //put quote in array
});
callback(data) // here
console.log("quotes: ", quotes);
});
}
However, this definition of loadData is suboptimal at best. It should make use of the thenable return by jQuery's $.getJson, to make the code clearer.
function loadData(symbol) {
return $.getJSON("http://localhost:8080/springboot-crud-rest/api/v1/quotes-between?symbol=IBM&startDate=2020-01-01&endDate=2020-09-30")
.then(function (data) {
const quotes = data.map(function (item) {
return {
date: item.id.date.substring(0, 10),
...item
};
});
console.log("quotes: ", quotes);
return quotes;
});
}
Consumed as
loadData('something').then(function (data) {
console.log("data: ", data);
});
How to pass data from promise then method to an object method.
this.httpReq(url).then(function (data) {
this.storeData(data);
});
I know that here I'm out of scope and this doesn't refer to my object. But, nevertheless, I can't understand how to resolve it.
Bellow you can find entire code snippet. In the end, I want to get data from the service API and store it in the object array property this.storage.
var http = require('http');
function CoubApi (url) {
this.url = url;
this.storage = [];
this.httpReq = httpReq;
this.searchData = searchData;
this.storeData = storeData;
}
function httpReq (url) {
var promise = new Promise (function (resolve, reject) {
http.get(url, function (res) {
var data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function () {
if(data.length > 0) {
resolve(JSON.parse(data));
} else {
reject("Error: HTTP request rejected!");
}
});
}).on('error', function (err) {
console.log("Error: ", e);
});
});
return promise;
}
function storeData (data) {
var i;
console.log("Storrrreee");
for(i = 0; i < 10; i++) {
this.storage.push(data.coubs[i]);
}
}
function searchData (searchtext, order, page) {
var url = this.url+
"search?q="+searchtext+
"&order_by="+order+
"&page="+page;
this.httpReq(url).then(function (data) {
this.storeData(data);
});
}
var coub = new CoubApi("http://coub.com/api/v2/");
coub.searchData("cat", "newest_popular", 1);
console.log(coub.storage);
You can store this in varaible:
var self = this;
this.httpReq(url).then(function (data) {
self.storeData(data);
});
or use bind:
this.httpReq(url).then(function (data) {
this.storeData(data);
}.bind(this));
When I get a request, I want it to generate a 4-character code, then check if it already exists in the database. If it does, then generate a new code. If not, add it and move on. This is what I have so far:
var code = "";
var codeFree = false;
while (! codeFree) {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var code = "";
for (var i = 0; i < 4; i++) {
var rand = Math.floor(Math.random() * chars.length);
console.log(rand);
code += chars.charAt(rand);
}
console.log("Code: %s generated.", code);
client.execute("select * from codes where code=" + code, function(err, result) {
if (! err) {
if (result.rows.length > 0) {
codeFree = false;
} else {
codeFree = true;
}
} else {
console.log('DB ERR: %s', err);
}
console.log(codeFree);
});
console.log('here');
}
This does not do nearly what I want it to do. How can I handle something like this?
You are doing an async task.
When you have an asyncronous task inside your procedure, you need to have a callback function which is going to be called with the desired value as its argument.
When you found the free code, you call the function and passing the code as its argument, otherwise, you call the getFreeCode function again and passing the same callback to it. Although you might consider cases when an error happens. If your the db call fails, your callback would never get called. It is better to use a throw/catch mechanism or passing another argument for error to your callback.
You can achieve what you need to do by doing it this way:
function getFreeCode(callback) {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var code = "";
for (var i = 0; i < 4; i++) {
var rand = Math.floor(Math.random() * chars.length);
console.log(rand);
code += chars.charAt(rand);
}
console.log("Code: %s generated.", code);
client.execute("select * from codes where code="+code, function(err, result) {
if(!err) {
if(result.rows.length > 0) {
getFreeCode(callback);
} else {
callback(code);
}
}else {
console.log('DB ERR: %s', err);
}
console.log(codeFree);
});
console.log('here');
}
// in your main:
getFreeCode(function (code) {
console.log(' this code was free: ' + code)
})
I recommend you look into two alternatives to help deal with asynchronous code.
node generator functions using the 'yield' keyword
promises
Using generators requires running a recent version of node with the --harmony flag. The reason I recommend generators is because you can write code that flows the way you expect.
var x = yield asyncFunction();
console.log('x = ' + x);
The previous code will get the value of x before logging x.
Without yielding the console.log would write out x before the async function was finished getting the value for x.
Your code could look like this with generators:
var client = {
execute: function (query) {
var timesRan = 0;
var result = [];
return function () {
return setTimeout(function () {
result = ++timesRan < 4 ? ['length_will_be_1'] : [];
return result;
},1);
};
}
};
function* checkCode () {
var code;
var codeFree = false;
while(!codeFree) {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
code = "";
for (var i = 0; i < 4; i++) {
var rand = Math.floor(Math.random() * chars.length);
console.log(rand);
code += chars.charAt(rand);
}
console.log("Code: %s generated.", code);
try {
var result = yield client.execute("select * from codes where code="+code);
codeFree = result.rows.length > 0 ? false : true;
}catch(e) {
console.log('DB ERR: %s', err);
} finally {
console.log(codeFree);
}
console.log('here');
}
}
checkCode().next();
You would leave off the client object. I only added that to make a working example that fakes an async call.
If you have to use an older version of node or do not like the yield syntax then promises could be a worthy option.
There are many promise libraries. The reason I recommend promises is that you can write code that flows the way you expect:
asyncGetX()
.then(function (x) {
console.log('x: ' + x);
});
The previous code will get the value of x before logging x.
It also lets you chain async functions and runs them in order:
asyncFunction1()
.then(function (result) {
return asyncFunction2(result)
})
.then(function (x) { /* <-- x is the return value from asyncFunction2 which used the result value of asyncFunction1 */
console.log('x: ' + x);
});
Your code could look like this with the 'q' promise library:
var Q = require('q');
var client = {
timesRan: 0,
execute: function (query, callback) {
var self = this;
var result = {};
setTimeout(function () {
console.log('self.timesRan: ' + self.timesRan);
result.rows = ++self.timesRan < 4 ? ['length = 1'] : [];
callback(null, result);
},1);
}
};
function checkCode () {
var deferred = Q.defer();
var codeFree = false;
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var code = "";
for (var i = 0; i < 4; i++) {
var rand = Math.floor(Math.random() * chars.length);
console.log('rand: %s', rand);
code += chars.charAt(rand);
}
console.log("Code: %s generated.", code);
client.execute("select * from codes where code="+code, function(err, result) {
console.log('err: '+err+', result: ' + JSON.stringify(result));
console.log('result.rows.length: ' + result.rows.length);
if(!err) {
if(result.rows.length > 0) {
codeFree = false;
console.log('result.rows: %s, codeFree: %s', result.rows, codeFree);
checkCode();
} else {
codeFree = true;
console.log('line 36: codeFree: ' + codeFree);
deferred.resolve(code);
}
}else {
console.log('DB ERR: %s', err);
deferred.reject(err);
}
console.log(codeFree);
});
console.log('waiting for promise');
return deferred.promise;
}
checkCode()
.then(function (code) {
console.log('success with code: ' + code);
})
.fail(function(err) {
console.log('failure, err: ' + err);
});
Also omit the client object here. I only added that to make a working example that fakes an async call.
Promises and generators definitely take some time to get used to. It's worth it because they make the code a lot easier to follow in the end than code written with nested callbacks.
Here are the cloud functions namely 'batchReq1' and batchPromises.
In any case, if I know the exact number of promises pushed (Consider the size of results to be '2' in function batchPromises(results)) and executed through when(), I can handle the success response by passing that number of result parameters (In the below example request1, request2) in successCallBack of .then().
If I have to process the number of promises pushed to .when() dynamically, then, how can we handle this in SuccessCallBack? Unlike earlier scenario, we can't expect fixed number of results in the then method (batchPromises(results).then(function (result1, result2) {....)
batchReq1
Parse.Cloud.define("batchReq1", function (request, response) {
var results = request.params.imageArray;
batchPromises(results).then(function (result1, result2) {
console.log("Final Result:: Inside Success");
console.log("Final Result:: Inside Success result 1::::"+result1);
console.log("Final Result:: Inside Success result 2::::"+result2);
response.success();
}
// batchPromises(results).then(function (arraySuccess) {
//
// console.log("Final Result:: Inside Success");
// console.log("Final Result:: Inside Success:: Length:: "+arraySuccess.length);
// //Fetch all responses from promises and display
// var _ = require('underscore.js');
// _.each(arraySuccess, function (result) {
//
// console.log("Final Result:: " + result)
//
// });
//
//
// response.success();
//
// }
, function (error) {
console.log("Final Result:: Inside Error");
response.error(error);
});
});
batchPromises
function batchPromises(results) {
var promise = Parse.Promise.as();
var promises = [];
var increment = 0;
var isFromParallelExecution = false;
var _ = require('underscore.js');
_.each(results, function (result) {
var tempPromise = Parse.Promise.as("Promise Resolved ");
promises.push(tempPromise);
}
promise = promise.then(function () {
return Parse.Promise.when(promises);
});
}
increment++;
});
return promise;
}
this is how i handle this...
Parse.Cloud.define("xxx", function(request, response)
{
var channels = ["channel1", "channel2", "channel3", "channel4", "channel5"];
var queries = new Array();
for (var i = 0; i < channels.length; i++)
{
var query = new Parse.Query(channels[i]);
queries.push(query.find());
}
Parse.Promise.when(queries).then(function()
{
var msg = "";
for (var j = 0; j < arguments.length; j++)
{
for (var k = 0; k < arguments[j].length; k++)
{
var object = arguments[j][k];
msg = msg + " " + object.get('somefield');
}
}
response.success(msg);
});
});
Either you just use the arguments object to loop over the results, or you build your arraySuccess without when - it doesn't make much sense here anyway as you batch the requests (executing them sequentially), instead of executing them in parallel:
function batchPromises(tasks) {
var _ = require('underscore.js');
_.reduce(tasks, function (promise, task) {
return promise.then(function(resultArr) {
var tempPromise = Parse.Promise.as("Promise Resolved for "+taks);
return tempPromise.then(function(taskResult) {
resultArr.push(taskResult);
return resultArr;
});
});
}, Parse.Promise.as([]));
}
If you actually wanted to execute them in parallel, use a simple
function batchPromises(tasks) {
var _ = require('underscore.js');
return Parse.Promise.when(_.map(tasks, function (task) {
return Parse.Promise.as("Promise Resolved for "+taks);
}).then(function() {
return [].slice.call(arguments);
});
}
I do a async call in a for loop and i know that the response is coming async but how can i get my response always in the same order. Here's my code:
setInterval(function () {
callback = function (response)
{
var temp2 = '';
var str = "";
test = [];
console.log('STATUS: ' + response.statusCode);
response.setEncoding('utf8');
response.on('data', function (chunk)
{
str += chunk;
});
response.on('end', function ()
{
console.log("end found");
temp2 = JSON.parse(str);
for (var i in temp2['build'])
{
test.push(temp2['build'][i]['id']);
var req3 = http.request({
host: host, // here only the domain name
auth: auth,
port: 8111,
path: '/httpAuth/app/rest/builds/id:' + test[i] + '/statistics/', // the rest of the url with parameters if needed
method: 'GET', // do GET
headers: { "Accept": "application/json" }
}, callback2);
req3.end();
}
});
}
var req4 = http.request(options4, callback);
req4.end();
callback2 = function (response) {
//console.log('STATUS: ' + response.statusCode);
//console.log('HEADERS: ' + JSON.stringify(response.headers));
response.setEncoding('utf8');
var str2 = "";
response.on('data', function (chunk) {
str2 += chunk;
});
response.on('end', function () {
points.push(parseInt(JSON.parse(str2)["property"][2]["value"]));
});
j++;
if (j == test.length) {
var sumTotal = 0;
var sumThree = 0;
var status = '';
for (var i in points) {
sumTotal += points[i];
}
var averageTotal = parseInt(Math.round(sumTotal / points.length));
for (var i = 0; i < 3; i++) {
sumThree += points[i];
}
var averageThree = parseInt(Math.round(sumThree / 3));
/*if(averageThree>averageTotal)
{
status='warning';
}
else
{
status='ok';
}*/
console.log('average: ' + averageThree + ' average 100 ' + averageTotal + ' status ' + status);
//send_event('speed', {current: averageThree/*, status: status*/, last: averageTotal});
j = 0;
points = [];
}
}
}, 15 * 1000);
so my question is how can i be sure my response 'point's' have always the same order. I've tried sending the var i to the callback function but can't get it to work
edit:
changed formatting.
The output of the first callback:
{
"count":100,
"nextHref":"/httpAuth/app/rest/builds/?locator=buildType:bt2,count:100,status:SUCCESS,start:100",
"build":[
{
"id":17469,
"number":"5075",
"status":"SUCCESS",
"buildTypeId":"bt2",
"startDate":"20140224T183152+0100",
"href":"/httpAuth/app/rest/builds/id:17469",
"webUrl":"http://x.x.x.x:8111/viewLog.html?buildId=17469&buildTypeId=bt2"
},
{
"id":17464,
"number":"5074",
"status":"SUCCESS",
"buildTypeId":"bt2",
"startDate":"20140224T165758+0100",
"href":"/httpAuth/app/rest/builds/id:17464",
"webUrl":"http://x.x.x.x:8111/viewLog.html?buildId=17464&buildTypeId=bt2"
},
{
"id":17461,
"number":"5073",
"status":"SUCCESS",
"buildTypeId":"bt2",
"startDate":"20140224T161852+0100",
"href":"/httpAuth/app/rest/builds/id:17461",
"webUrl":"http://x.x.x.x:8111/viewLog.html?buildId=17461&buildTypeId=bt2"
},
This output contains 100 items. From this output I take the id number and make a new request with this array of id's. This new callback gives me the build duration but the problem is because this happens asynchronously the response I get is not from the latest build but from the first response. So my question is how can i get these build speed array in the right order
It's not recommended to use anonymous function in a for loop.
The best way (for me), it's to use the async library.
A simple exemple to answer your question :
var objectList = [
{"name":"Doe", "firstname":"John", "position":1},
{"name":"Foo", "firstname":"Bar", "position":2},
{"name":"Gates", "firstname":"Bill", "position":3},
{"name":"Jobs", "firstname":"Steve", "position":4},
];
var arr = [];
async.each(objectList, function(person, callback) {
arr.push(person); // async.each is asynchronous, so at the end, the order will be bad
}, function(err) {
async.sortBy(arr, function(p, callback) { // Reorder
callback(err, p.position);
}, function(err, results) {
callback(null, results); // Send the result
});
});
This example will work for your issue.