I want to request multiple pages with request in async.forEach. I have several links, and I want to go to each link and get the sublinks from it. Then I want to evaluate each sublink and push results to dataarray. But only the first one sublink is evaluated multiple times, for each links array element. But I want to evaluate each sublink. Here is my code:
var dataarray = [];
var links = ["www.mysite.com/1","www.mysite.com/2","www.mysite.com/3"];
async.whilst(
function() { console.log("Number of links left: " + links.length); return links.length > 0; },
function(innerCallback){
var i1 = getRandomInt(1, links.length); //getRandomInt is a function that gives a random integer in array.
var Theurl = links[i1-1];
request({
url: Theurl,
//More paramaters to request here...
}, function(error, response, body) {
if (response) {
//Evaluate some jquery to get a variable allsublinks used later on.
//So I get several sub-links in each element in links array.
var allsublinks = stuff;
//Now I define a function to be used in async.forEach. I basically need to
//loop over allsublinks array that contains different url links.
function getInfo(name, thiscallback) {
console.log("Online");
console.log("Current url: "+name);
request({
url: name,
//Other parameters to request.
}, function(error, response, body) {
if (response) {
//Here I evaluate some jquery again to get the datafromthislink variable
dataarray.push(datafromthislink);
setTimeout(function() { thiscallback(); });
}
})
}
//Now I use async.forEach to loop over allstorelinks.
async.forEach(allsublinks, getInfo, function(err, results) {
console.log("dataarray: ",dataarray);
setTimeout(function() { links.splice(i1-1, 1); innerCallback(); });
});
}
})
},
function(err){
console.log("All Done");
})
What am I doing wrong?
Best Regards
This could be the cleaner version of what you are trying to achieve, which clear your concept of using callbacks properly.
var dataarray = [];
var links = ["www.mysite.com/1", "www.mysite.com/2", "www.mysite.com/3"];
//Now I define a function to be used in async.forEach. I basically need to
//loop over allsublinks array that contains different url links.
function getInfo(link, infoCallback) {
console.log("Online");
console.log("Current url: " + link);
request({
url: link,
//Other parameters to request.
}, function (error, response, body) {
if (error) console.log(error);
if (response) {
//Here I evaluate some jquery again to get the datafromthislink variable
dataarray.push(body.datafromthislink);
}
infoCallback();
})
}
function getLink(link, linkCallback) {
console.log('checking link: ' + link);
request({
url: link,
//More paramaters to request here...
}, function (error, response, body) {
if (error) console.log(error);
if (response) {
//Evaluate some jquery to get a variable allsublinks used later on.
//So I get several sub-links in each element in links array.
var allsublinks = body.stuff;
//Now I use async.forEach to loop over allstorelinks.
async.forEach(allsublinks, getInfo, linkCallback);
} else {
linkCallback();
}
})
}
async.each(links, getLink, function (err) {
if (error) console.log(error);
console.log("All Done");
console.log(dataarray);
});
Related
I have an array of items that I need to post to my server. I've tried the following but i never iterates.
var i = 0;
while (i < numOfItems) {
var item = items[i];
var a;
for(var ik in item){
console.log(item[ik]);
a = item[ik]; // Gets the key
break;
}
var formData = {
ID : ID,
UID : UID,
item : a
}
request.post({url:'http://example.com/a', formData: formData}, function(err, httpResponse, body){
if (err) {
return console.error('Post failed:', err);
}
console.log('Post successful! Server responded with:', body);
i++;
});
}
Your code won't work because request.post is asynchronous. If your objective is to make a call for each element in the array, a working and a more elegant solution would be to use Promises.all().
Here's your code modified with Promises —
function postRequest(url, formData) {
return new Promise((resolve, reject) => {
request.post({ url, formData }, function (err, httpResponse, body) {
if (!error) {
resolve({ message: 'Post successful!', response: body });
} else {
reject(err);
}
});
})
}
// Map your data array to an array of Promises
let promises = yourArray.map(element => {
let formData = {
ID: ID,
UID: UID,
item: element
}
return postRequest({ url: 'http://example.com/a', formData: formData })
});
// Wait for all Promises to complete
Promise.all(promises)
.then(results => {
// Handle results
})
.catch(e => {
// Handle error
});
A few things to note -
I'm reusing the fields ID and UID as-is, as it isn't clear where they come from in your code.
Replace yourArray with the array containing your data items.
The application is making API request to the server and returning back the response received. Here would like to understand
(i) Is it right way to make API call and receive response from the server.
(ii) if return statement passing back the value is right way
The module is called as
str = apiRequest(search, lang);
And the module is
var https = require('https');
function apiRequest(search, lang) {
var options = {
host: 'localhost:8080',
path: '/api?search=' + search + '&lang=' + lang
};
function resData(res) {
console.log("Status Code : ", res.statusCode);
var str = '';
res.on('data', function (chunk) {
str += chunk;
});
res.on('end', function () {
return JSON.parse(str); // Is this a right way to return data
});
res.on('error', function(e) {
console.error(e);
});
}
https.get(options, resData).end();
};
No, this will not work. I will comment the relevant parts below.
Instead of having this in apiRequest:
function apiRequest(search, lang) {
function resData(res) {
// ...
res.on('end', function () {
return JSON.parse(str); // Is this a right way to return data
});
// ...
}
https.get(options, resData).end();
}
and calling it with:
str = apiRequest(search, lang);
You should either pass a callback or return a promise.
Using callbacks
Your apiRequest function can take an additional argument, a callback:
function apiRequest(search, lang, callback) {
function resData(res) {
// ...
res.on('end', function () {
callback(null, JSON.parse(str));
});
res.on('error', function(e) {
console.error(e);
callback(e);
});
// ...
}
https.get(options, resData).end();
}
Now you can use it as:
apiRequest(search, lang, function (error, str) {
if (err) {
// you have error
} else {
// you have your str here
}
});
Using promises
Your apiRequest function can return a promise:
function apiRequest(search, lang, callback) {
return new Promise(function (resolve, reject) {
function resData(res) {
// ...
res.on('end', function () {
resolve(JSON.parse(str));
});
res.on('error', function(e) {
console.error(e);
reject(e);
});
// ...
}
https.get(options, resData).end();
}
}
Now you can use it as:
apiRequest(search, lang)
.then(function (str) {
// you have your str here
})
.catch(function (err) {
// you have error
});
This is not tested so there might be some minor mistakes but that is the general idea. When I find some errors I'll update the answer.
Summary
To sum it up, there are two styles that you can use to compose asynchronous functions like that: callbacks or promises. You will not be able to just return the data because return is fundamentally synchronous - you have to have something to return right away - unless what you return is a promise that can get resolved or rejected later.
Parsing JSON
What should also keep in mind that you should always run JSON.parse() inside a try {} catch {} block to handle errors of incorrect JSON or otherwise the entire app could crash. JSON.parse() throws exceptions on bad input. See this answer for more info.
This will additionally complicate your code but you can avoid that complication and make it even simpler by using the request module - see updates below.
Simpler examples
Callbacks
To have a working example that is simpler and your don't have to manually parse JSON, consider this code that I just wrote, based on your example but calling a GitHub API so it can be tested by everyone - it prints someone's website given his GitHub nickname but otherwise works similarly to your code:
'use strict';
var request = require('request');
function apiRequest(search, callback) {
var options = {
url: 'https://api.github.com/users/' + search,
json: true,
headers: {'User-Agent': 'request'}
};
function resData(err, res, data) {
if (err) {
callback(err);
} else if (res.statusCode !== 200) {
callback(res.statusCode);
} else {
// data is already parsed as JSON:
callback(null, data.blog);
}
}
request.get(options, resData);
}
apiRequest('rsp', function (err, data) {
if (err) {
console.log('Error:', err);
} else {
console.log('Data:', data);
}
});
This is an example using callbacks.
Promises
And here is an example using promises:
'use strict';
var request = require('request');
function apiRequest(search, callback) {
return new Promise(function (resolve, reject) {
var options = {
url: 'https://api.github.com/users/' + search,
json: true,
headers: {'User-Agent': 'request'}
};
function resData(err, res, data) {
if (err) {
reject(err);
} else if (res.statusCode !== 200) {
reject(res.statusCode);
} else {
// data is already parsed as JSON:
resolve(data.blog);
}
}
request.get(options, resData);
});
}
apiRequest('rsp')
.then(function (data) {
console.log('Data:', data);
})
.catch(function (err) {
console.log('Error:', err);
});
Simplified
It can be simplified even further by using fat arrow functions and anonymous functions and object literals:
'use strict';
var request = require('request');
function apiRequest(search, callback) {
return new Promise((resolve, reject) => {
request.get({
url: 'https://api.github.com/users/' + search,
json: true,
headers: {'User-Agent': 'request'}
}, (err, res, data) => {
if (err) {
reject(err);
} else if (res.statusCode !== 200) {
reject(res.statusCode);
} else {
resolve(data.blog);
}
});
});
}
apiRequest('rsp')
.then(data => console.log('Data:', data))
.catch(err => console.log('Error:', err));
More info
You can see some other answers where I explain the difference between callbacks and promises and how to use the together in more detail, which you may find helpful:
A detailed explanation on how to use callbacks and promises
Explanation on how to use promises in complex request handlers
An explanation of what a promise really is, on the example of AJAX requests
An explanation on how to handle errors in callbacks and promises
I am building a nodejs application.
Location.find({} ,function (err, result){
var locations = [];
result.forEach(function(listItem, i){
url = "https://maps.googleapis.com/maps/api/geocode/json?address=" + encodeURIComponent(listItem.address) + "&key=AIzaSyDQsX4Kci3xHp1xy6ZjT-5lsLNI-J-CH-8";
request(url, function(error, response, body) {
all = JSON.parse(body);
locations.push({des: result[i].placeinfo,lat: all.results[0].geometry.location.lat, lng: all.results[0].geometry.location.lng });
if (i == result.length - 1) {
res.render("index.ejs", { layout: false,locationmap:locations});
}
});
});
});
I have two problems here.
My loop runs 4 times, when i try to console.log() the i var its shows 4 time in the console.
why cant i use the request body outside of the loop i did some workaround and did the res.render inside the if statement.
You may like to use asyncjs eachSeries function but you need to install(npm install asyncjs --save) and then use like
var async = require('asyncjs');
Location.find({} ,function (err, result){
var locations = [];
async.eachSeries(result, function iteratee(listItem, callback) {
url = "https://maps.googleapis.com/maps/api/geocode/json?address=" + encodeURIComponent(listItem.address) + "&key=AIzaSyDQsX4Kci3xHp1xy6ZjT-5lsLNI-J-CH-8";
request(url, function(error, response, body) {
all = JSON.parse(body);
locations.push({des: result[i].placeinfo,lat: all.results[0].geometry.location.lat, lng: all.results[0].geometry.location.lng });
callback(error, body);
});
}, function done() {
res.render("index.ejs", { layout: false,locationmap:locations});
});
});
Probably an obvious answer to this but I'm not sure what way to take.
request is a node module: https://github.com/request/request
I fill an array of getHistory requests (with different parameters). p = [p1,p2...].
this.app.all('/api/heatmap', function(req,res) {
// fill p here _.each(blabla, p.push(gethistory(someparams...)
var result = [];
function getHistory(params) {
var options = { ...};
var callback = function(error, response, body) {
if(error) { //wtv
} else {
// what to do with the body here ? return body ? result.push(body) ?
}
}
request(options, callback);
}
Q.all(p).then(function() {
});
}
So the problem here is that I when all of the request to be done , put everything in an array/object then send the whole thing to the client. How to have getHistory returning the fetched value (after the request is done ).
Hope it's clear.
The core problem here is that node.js-style callbacks and promises are not compatible. Promises emphasize on return values, node emphasizes on callbacks.
Therefore you need a sort of adapter that wraps node's callback convention properly, a process called Promisifcation. This can be done manually, but it's tedious at best and error-prone when you are not careful. Luckily, since node's conventions are well-established, it can be automated. Q has a few helpers for that, but Bluebird is quite a bit more convenient in this regard.
So the easy way to do it is to switch to Bluebird as the promise library and to use promisifyAll.
var Promise = require('bluebird');
var request = Promise.promisifyAll(require("request"));
this.app.all('/api/heatmap', function(req, res) {
var requests = blabla.map(function (item) {
return request.getAsync({ /* params */ });
});
Promise.all(requests).then(function (responses) {
res.send( JSON.stringify(responses) ); // whatever
}).catch(function (error) {
res.send( "An error ocurred: " + error ); // handle error
});
}
FWIW, here's another answer that shows how the same would look like when done properly with Q:
// promisified request
function requestAsync(options) {
var result = Q.defer();
request(options, function(error, response, body) {
if (error) {
result.reject(error);
} else {
result.resolve(body);
}
});
return result.promise;
}
// returns promises for heatmapVolumes
function getHistory(params) {
return requestAsync({
method: 'GET',
url: 'https://api.kaiko.com/v1/charts/' +
encodeURIComponent(params.exchange) + '/' +
encodeURIComponent(params.pair) +'/history',
qs: params.qs,
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
}).then(function (body) {
return heatmapVolume(body, params.exchange, params.pair);
}).catch(function (error) {
// log detailed error and send less revealing message downstream
console.error('error fetching trades', error);
throw new Error('Something went wrong while fetching trades');
});
}
// usage
this.app.all('/api/heatmap', function(req, res) {
getHistory({
exchange: "foo", pair: "bar", qs: "qux"
}).then(function (heatmap) {
res.send(200, heatmap);
}).catch(function (error) {
res.send(500, error);
});
});
Used Q.deferred and it worked as documented \o/
function getHistory(params) {
var deferred = Q.defer();
var options = {
method: 'GET',
url: 'https://api.kaiko.com/v1/charts/' + params.exchange + '/' + params.pair +'/history',
qs:qs,
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
}
var callback = function(error, response, body) {
if(error) {
console.log('error fetching trades', error);
res.send(500, 'Something went wrong while fetching trades');
} else {
var body = heatmapVolume(body, params.exchange, params.pair);
// console.log("result!", body.exchange, body.pair);
result.push(body);
// return body;
deferred.resolve();
}
}
request(options, callback);
return deferred.promise;
}
While building a fairly complex scraper i stumbled upon a problem with a control flow of my code.
What's going on in code below:
1) request a URL
2) scrape NEWURL from the results
3) pass it to readability API as first async function
4) here comes the trouble — i never get the next async function which saves readabilityData to DB
How to solve this problem?
I'm new to JS, so please feel free to point out at any issues with my code.
request(URL, function(error, response, html) {
if (!error) {
var $ = cheerio.load(html);
NEWURL = data.find('a').attr('href');
readabilityData = {}
var articleUrl = 'https://readability.com/api/content/v1/parser?url=' + NEWURL + token;
async.series([
function(){
request(articleUrl, function(error, response, html) {
if (!error) {
readabilityData = response.toJSON();
}
});
},
function(readabilityData){
Article.findOne({
"link": url // here's the
}, function(err, link){
if(link) {
console.log(link)
} else {
var newArticle = new Article({
// write stuff to DB
});
newArticle.save(function (err, data) {
// save it
});
}
});
}
],
function(err){
console.log('all good — data written')
});
});
}
});
You need to call the callback parameter that's passed into the functions of the async.series call when each function's work is complete. That's how async.series knows that it can proceed to the next function. And don't redefine readabilityData as a function parameter when you're trying to use it to share data across the functions.
So something like:
var readabilityData = {};
async.series([
function(callback){
request(articleUrl, function(error, response, html) {
if (!error) {
readabilityData = response.toJSON();
}
callback(error);
});
},
function(callback){
Article.findOne({
"link": url // here's the
}, function(err, link){
if(link) {
console.log(link);
callback();
} else {
var newArticle = new Article({
// write stuff to DB
});
newArticle.save(function (err, data) {
// save it
callback(err);
});
}
});
}
],
function(err){
console.log('all good — data written')
});