Handling multiple call asynchronous callbacks - javascript

I am learning node.js with learnyounode.
I am having a problem with JUGGLING ASYNC.
The problem is described as follows:
You are given three urls as command line arguments. You are supposed to make http.get() calls to get data from these urls and then print them in the same order as their order in the list of arguments.
Here is my code:
var http = require('http')
var truecount = 0;
var printlist = []
for(var i = 2; i < process.argv.length; i++) {
http.get(process.argv[i], function(response) {
var printdata = "";
response.setEncoding('utf8');
response.on('data', function(data) {
printdata += data;
})
response.on('end', function() {
truecount += 1
printlist.push(printdata)
if(truecount == 3) {
printlist.forEach(function(item) {
console.log(item)
})
}
})
})
}
Here is the questions I do not understand:
I am trying to store the completed data in response.on('end', function(){})for each url using a dictionary. However, I do not know how to get the url for that http.get(). If I can do a local variable inside http.get(), that would be great but I think whenever I declare a variable as var url, it will always point to the last url. Since it is global and it keeps updating through the loop. What is the best way for me to store those completed data as the value with the key equal to the url?

This is how I would go about solving the problem.
#!/usr/bin/env node
var http = require('http');
var argv = process.argv.splice(2),
truecount = argv.length,
pages = [];
function printUrls() {
if (--truecount > 0)
return;
for (i = 0; i < pages.length; i++) {
console.log(pages[i].data + '\n\n');
}
}
function HTMLPage(url) {
var _page = this;
_page.data = '### [URL](' + url + ')\n';
http.get(url, function(res) {
res.setEncoding('utf8');
res.on('data', function(data) {
_page.data += data;
});
res.on('end', printUrls);
});
}
for (var i = 0; i < argv.length; i++)
pages.push(new HTMLPage(argv[i]));
It adds the requests to an array on the start of each request, that way once done I can iterate nicely through the responses knowing that they are in the correct order.
When dealing with asynchronous processing, I find it much easier to think about each process as something with a concrete beginning and end. If you require the order of the requests to be preserved then the entry must be made on creation of each process, and then you refer back to that record on completion. Only then can you guarantee that you have things in the right order.
If you were desperate to use your above method, then you could define a variable inside your get callback closure and use that to store the urls, that way you wouldn't end up with the last url overwriting your variables. If you do go this way though, you'll dramatically increase your overhead when you have to use your urls from process.argv to access each response in that order. I wouldn't advise it.

I went about this challenge a little differently. I'm creating an array of functions that call http.get, and immediately invoking them with their specifc context. The streams write to an object where the key is the port of the server which that stream is relevant to. When the end event is triggered, it adds to that server to the completed array - when that array is full it iterates through and echos in the original order the servers were given.
There's no right way but there are probably a dozen or more ways. Wanted to share mine.
var http = require('http'),
request = [],
dataStrings = {},
orderOfServerInputs = [];
var completeResponses = [];
for(server in process.argv){
if(server >= 2){
orderOfServerInputs[orderOfServerInputs.length] = process.argv[server].substr(-4);
request[request.length] = function(thisServer){
http.get(process.argv[server], function(response){
response.on("data", function(data){
dataStrings[thisServer.substr(-4)] = dataStrings[thisServer.substr(-4)] ? dataStrings[thisServer.substr(-4)] : ''; //if not set set to ''
dataStrings[thisServer.substr(-4)] += data.toString();
});
response.on("end", function(data){
completeResponses[completeResponses.length] = true;
if(completeResponses.length > 2){
for(item in orderOfServerInputs){
serverNo = orderOfServerInputs[item].substr(-4)
console.log(dataStrings[serverNo]);
}
}
});
});
}(process.argv[server]);
}
}

Immediately-Invoked Function Expression (IIFE) could be a solution to your problem. It allows us to bind to function a specific value, in your case, the url which gets the response. In the code below, I bind variable i to index and so, whichever url gets the response, that index of print list will be updated. For more information, refer to this website
var http = require('http')
var truecount = 0;
var printlist = [];
for(var i = 2; i < process.argv.length; i++) {
(function(index){
http.get(process.argv[index], function(response) {
response.setEncoding('utf8');
response.on('data', function(data) {
if (printlist[index] == undefined)
printlist[index] = data;
else
printlist[index]+= data;
})
response.on('end', function() {
truecount += 1
if(truecount == 3) {
printlist.forEach(function(item) {
console.log(item)
})
}
})
})
})(i)
}

Related

How i can pass information into a GET in node

i dont know how i can pass some information into a GET in node. I have a .txt file and sometimes have a change in this file, so, i want to show this change when i enter in my webpage, but the change only occurs when the server refresh.
var itens = func.readfile();
app.get("/inventario", function(req, res){
res.format({
html: function(){
res.render('inventario', {itenss: func.readfile()});
}
});
i have a variable that receive a data from a function in other file.
exports.readfile = function() {
var texto = [];
fs.readFile('./static/itens.txt', function(err, data) {
if (err) {
return console.error(err);
}
var palavra = '';
var linha = [];
for (var i = 0; i < data.length; i++) {
if (data[i] != 10) {
palavra = palavra + (String.fromCharCode(data[i]));
} else if (data[i] == 10) {
texto.push(palavra);
palavra = [];
}
}
console.log(texto);
});
return texto;
}
so if the variable is out of scope, the page can receive the data and show in html file, but, i want to refresh the data in array case i refresh the page and the new information in .txt file is shown too
fs.readFile() is asynchronous. That means that when you call the function, it starts the operation and then immediately returns control back to the function and Javascript busily executes more Javascript in your function. In fact, your readFile() function will return BEFORE fs.readFile() has called its callback. So, you cannot return the data directly from that function. Instead, you will need to either return a promise (that resolves when the data is eventually available) or you will need to communicate the result back to the caller using a callback function.
See this answer: How do I return the response from an asynchronous call? for a lot more info on the topic.
You can modify your readFile() function to return a promise (all async functions return a promise) like this:
const fsp = require('fs').promises;
exports.readfile = async function() {
let texto = [];
let data = await fsp.readFile('./static/itens.txt');
let palavra = '';
let linha = [];
for (var i = 0; i < data.length; i++) {
if (data[i] != 10) {
palavra = palavra + (String.fromCharCode(data[i]));
} else if (data[i] == 10) {
texto.push(palavra);
palavra = [];
}
}
console.log(texto);
return texto;
}
And, then you can use that function like this:
app.get("/inventario", function(req, res){
res.format({
html: function(){
func.readFile().then(data => {
res.render('inventario', {itenss: data});
}).catch(err => {
console.log(err);
res.sendStatus(500);
});
}
});
For performance reasons, you could also cache this result and cache the time stamp of the itens.txt file and only reread and reprocess the file when it has a newer timestamp. I'll leave that as an exercise for you to implement if it is necessary (may not be needed).

Run ajax request until it returns results

I currently rely on a simple ajax call to fetch some data from our query service api. It is unfortunately not the most robust api and can at times return an empty result set. As such, I want to retry the ajax call until resultSet.length > 0.
I could use setTimeOut and break the loop if I find results, but it seems like an inelegant solution, especially as the time to completion is anywhere between 1s and 6s. I currently have the following, but it doesn't seem to break the loop when needed, and remains inelegant. Any help would be appreciated!
var resultSet = 0;
function fetchQueryData(query, time, iter) {
(function myLoop(i){
if (i == iter) {
fetchData(resultSet, dataset, query);
} else {
setTimeout(function(){
if (resultSet == 0) {
fetchData(resultSet, dataset, query);
}
if (--i) myLoop(i);
}, time)
}
})(iter);
}
fetchQueryData('select * from table', 6000, 5);
function fetchData(resultSet, dataset, query) {
var dataString = 'query=' + encodeURIComponent(query);
$.ajax({
type : "POST",
data: dataString,
url : "/queryapi",
success: function(json) {
var data = [];
var schema = json.data.schema;
var rows = json.data.rows;
if (typeof schema != 'undefined') {
resultSet = 1;
for (var i = 0; i < rows.length; i++) {
var obj = {};
for (var j = 0; j < schema.length; j++) {
obj[schema[j]['name']] = rows[i][j];
}
data.push(obj);
}
}
});
}
Instead of using a setTimeout, wrap the request in a function, and call that same function in the success callback of the request if the returned set is empty.
This will prevent you from sending more than one request to your API at a time, and will also terminate as soon as you get back a satisfactory response.
(In short, you're using recursion instead of an explicit loop.)

How to avoid creating a function inside a loop?

I am working on gathering some data using REST calls.
I have the following function which makes calls to a "directory" endpoint that returns me a list of people
I can get more information about their kids.
Now to get their personal information I will have to hit their individual APIs.
var listOfPeople = [];
var numberOfKids = [];
//using superagent
var req = request.get(ApiPrefix + "/directory");
req.end(function (err, res) {
if (err || !res.ok) {
console.log("Error obtaining directory");
} else {
listOfPeople.add(res.body);
// loop to retrieve all events.
for (var i = 0; i < res.body.length; i++) {
var personID = res.body[i].id;
$.getJSON('/directory/person', {
id: personID
},
function (result) {
numberOfKids.add(result);
});
}
}
});
While the above code works perfectly fine, I am getting an error from Gulp watch:
line 67, col 30, Don't make functions within a loop. (W083)
1 error
So, how do I decouple the loop and the AJAX call while expecting the exact same behavior?
I think you can remove for loop and start using Array.forEach
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
Supports all the latest browsers
In you case, Your for loop should be modified to
res.body.forEach(function(person){
var personId = person.id;
// your getJson call comes here
});

Why are my async callback functions not returning the data requests in order?

I'm learning Node.js and am working through the Learnyounode course from Node school which is amazing although it is quite a challenge and I'm and a bit confused...
The question I'm stuck on is Excercise 9- Juggling Async...
I'm trying to solve the problem:
"You must collect the complete content provided to you by each of the URLs and print it to the console (stdout). You don't need to print out the length, just the data as a String; one line per URL. The catch is that you must print them out in the same order as the URLs are provided to you as command-line arguments."
I've tried counting the callbacks in a variable called 'waiting' and then calling a complete() function when it has counted 3 callbacks, but the data still always comes back in the wrong order and I don't know why....
var http = require('http');
var bl = require('bl');
var url = []
url[0] = process.argv[2];
url[1] = process.argv[3];
url[2] = process.argv[4];
var waiting = 0;
for(i=0; i < url.length; i++) {
var output = [];
http.get( url[i], function(res) {
res.setEncoding('utf8');
res.pipe(bl(function(err,data) {
output.push(data.toString());
waiting++;
if(waiting == 3) {
complete(output);
}
}));
});
}
var complete = function(output) {
for(i=0; i < output.length; i++) {
console.log(output[i]);
}
}
The async callbacks are not guaranteed to occur in the order that the HTTP requests are made.
You could maintain the order by changing:
output.push(data.toString());
to:
output[i] = data.toString();
it's because your http get requests are asynchronous.
instead of pushing to output array do this
output[i]=data.toString();
Save the value of the 'i' index for each of you requests, either by closure or by adding it as a property to the request. You can then use that index to order your responses.

Meteor HTTP.call on server side correct using?

Sorry for my english.
I'm beginner JS developer. I need help with Meteor.
I try to write rss aggregator.
Can you tell me why this code not work correctly for me?
rssContent is always undefined.
But if I try console.log(result.content) inside HTTP.call I see the rss data.
I need to pass rssContent in another function in this file to parsing XML, but I have trouble with HTTP.call
server.js code:
var rssSources = ['http://news.yandex.ru/auto.rss'],
parsedRss = [];
var rssContent;
for (var i = 0; i < rssSources.length; i++) {
HTTP.call('GET', rssSources[i],
function(error, result) {
try {
rssContent = result.content;
} catch (e) {
console.log(e);
}
}
);
}
console.log(rssContent);
Your rssContent variable is always undefined because you are calling it inside callback(async) non-blocking method. Instead of callback method, you have to use sync(blocking) method.
I would suggest you, don't assigning value to a global variable, insert data directly it into the database whenever you got a response of your http request.
Blocking Example:-
var rssSources = ['http://news.yandex.ru/auto.rss'],
parsedRss = [];
var rssContent = "";
for (var i = 0; i < rssSources.length; i++) {
var result = Meteor.http.call("GET", rssSources[i]);
if(result.statusCode == '200' && result.content){
rssContent += result.content;
}
}
console.log(rssContent);
Non-Blocking Example:-
var rssSources = ['http://news.yandex.ru/auto.rss'],
for (var i = 0; i < rssSources.length; i++) {
HTTP.call('GET', rssSources[i],
function(error, result) {
try {
//HERE INSERT YOUR DATA INTO THE DATABASE.INSTEAD OF ASSIGNING THE VALUE TO GLOBAL VARIABLE.
} catch (e) {
console.log(e);
}
}
);
}
There are two ways of running an HTTP request in Meteor. It runs synchronously by default. If you pass a callback, it runs asynchronously instead. In this case, assuming this is server-side code, it's easier to omit the callback and treat it as synchronous (much less headache IMO).
var rssContent = [];
for (var i = 0; i < rssSources.length; i++) {
var result = HTTP.call('GET', rssSources[i]);
// error handling
resultContent.push(result);
}
I'm not familiar on handling errors for the synchronous version. You can head here for more details in their doc.

Categories

Resources