Starting out with Node without any async knowledge and am wondering how I can push data as part of a callback into an array. The output array is declared but is undefined when its referenced within the request callback. Is there a simple way to just pass that variable into the callback function scope?
Ideally, I'd like to send back an array of the results from each request made back to the caller.
const request = require('request');
module.exports = {
apiRatingCall: function (input, callback) {
var output = []
for (var i = 0; i < input.length; i++) {
var options = {
url: 'someAPIURL' + '?longitude=' + input[i].longitude + '&latitude=' + input[i].latitude + '&name=' + input[i].name,
headers: {
'x-api-version': 2
}
};
request(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
var info = JSON.parse(body)
output.push(info) // this is not working as ouput is undefined at this point
}
})
}
callback(output)
}
}
Thanks
my answer maybe not very normative in programming by it is only way for this problem I promise
module.exports = {
apiRatingCall: function (input, callback) {
var output = []
for (var i = 0; i < input.length; i++) {
var options = {
url: 'someAPIURL' + '?longitude=' + input[i].longitude + '&latitude=' + input[i].latitude + '&name=' + input[i].name,
headers: {
'x-api-version': 2
}
};
request(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
var info = JSON.parse(body)
output.push(info) // this is not working as ouput is undefined at this point
}
})
}
setTimeout(function(){
callback(output)
},500)
}
}
You can check the end of for loop and execute your callback
var loopCallback = function(error, response, body) {
if (!error && response.statusCode == 200) {
var info = JSON.parse(body)
output.push(info)
}
if( i == input.length - 1){ //checking index for completion of loop
callback(output);
}
}
request(options, loopCallback})
Take a look at the async module. It is a good module to do async operation with array and collection if you do not want to use bluebird and promises.
Do npm install async -S.
const request = require('request');
const async = require('async);
module.exports = {
apiRatingCall: function (inputs, callback) {
var output = []
async.each(inputs, function(input, cb) {
var options = {
url: 'someAPIURL' + '?longitude=' + input.longitude + '&latitude=' + input.latitude + '&name=' + input.name,
headers: {
'x-api-version': 2
}
request(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
var info = JSON.parse(body)
output.push(info);
cb();
}else{
//if any of the request fails then return error
cb(error);
}
})
}, function(err){
if(!err){
return callback(output);
}else{
//if any of the request return error
return callback(err);
}
});
}
Related
I'm using request to make HTTP requests inside a loop of variable size. (I will be reading words from a file, right now I just have an array). I'm adding onto an object (response) with each loop, and want to return the object only after the last request has been completed. I've been playing around with promises but I'm not sure how to chain them since it is a variable number of requests.
const request = require('request');
// eventually this array will be populated by reading a file
var words = ["hello", "there"];
var response = {};
// Loop thruogh input string
for (var i = 0; i < words.length; i += 1) {
// get next word
var curWord = words[i];
// if the current word is not already in the response
if (!(curWord in response)) {
// request info from dictionary
var options = {
url: 'https://api.dictionaryapi.dev/api/v2/entries/en/' + curWord,
json: true
}
request(options, (err, res, body) => {
if (err) { return console.log(err); }
// find part of speech
var partOfSpeech;
try {
partOfSpeech = body[0].meanings[0].partOfSpeech
} catch (err) {
partOfSpeech = "undefined";
}
// add to response
response[curWord] = partOfSpeech;
});
}
}
// do this part only after last request has been completed
console.log(response);
Basically you need to count the responses knowing that when counter reaches zero you are done with them all.
EDIT: make sure you are referring the same curWord parameter in the response to match the request. This is done using the IIFE there.
I have setup an example for you
const request = function fake_request(options, callback) {
setTimeout(function() {
callback(null, options.url.slice(options.url.lastIndexOf('/')))
}, Math.random() * 1000)
};
var words = ["hello", "there", "angela"];
var count = words.length;
var response = {}
for (var i = 0; i < words.length; i += 1) {
var curWord = words[i];
if (!(curWord in response)) {
var options = {
url: 'https://api.dictionaryapi.dev/api/v2/entries/en/' + curWord,
json: true
};
(function(curWord) {
request(options, (err, res, body) => {
count--
console.log(count)
partOfSpeech = res;
response[curWord] = partOfSpeech;
if (count == 0) {
// do this part only after last request has been completed
console.log(response);
}
});
})(curWord);
}
}
I'm trying to understand how to use function.
I have 2 functions inside the file functions.js:
getAllIssueForSCII()
test()
I have a another file (index.js) for call function but got some problem for call the function test().
functions.js
function getAllIssueForSCII(){
var options = {
...
};
return new Promise(function (resolve) {
request(options, function (error, response, data) {
if (!error && response.statusCode === 200) {
console.log('JIRA login success!!')
var json = JSON.parse(data);
resolve(json);
} else {
console.log('error: ' + response.statusCode + ': ' + data.statusMessage)
}
})
})
}
function test(){
/* We recover the value of the priority and we give it a score according to its value */
var priority = json.issues[i].fields.priority.name;
var score1 = 0 ;
switch (priority){
...
}
}
index.js
var functions = require('./functions.js')
functions.getAllIssueForSCII().then(function(json){
for (var i=0; i<json.issues.length;i++){
console.log(json.issues[i].key);
functions.test(); //Error
}
});
I have to make an API request, which then I then take an ID out of the data I get back and put that into another API request. This was not my API design pattern but it is what it is.
My issue is, when it comes to sending a JSON response, the response is being sent before all of my requests are finished. Now I have tried with counters to check that all my requests have been made before sending the response but it does not work. I will show you where the issue is:
So I make in total 26 API requests (crazy right).
I have gone through debugging and seen that my counter correctly increments 26 times.
This is where I check the counter:
if ((counter === counterSum)) {
res.send(projects);
}
The issue is, this code is running before all my counters can 'count'.
For example, at this point if I log the counter, it reaches to about 22 instead of 26. Often I see the counter log the same number 5 times or so because it has logged before my counter has had the chance to increment.
I have been going on this hours now so if anybody has an idea within the realm of Node.js, that would be amazing.
I will add some very simplified code showing all the request stages I have to go through and where I do the final log check:
let departmentProcesses = JSON.parse('[' + processes + ']');
Object.keys(departmentProcesses).forEach(function (key) {
console.log('The process' + departmentProcesses[key]);
let val = departmentProcesses[key];
let id = val.id;
let proccessURL = //url with + id
// Get all instances by project id
request({url: processURL},
function (error, response, body) {
//Convert the XML into JSON
parseString(response.body, function (err, result) {
departmentData = JSON.stringify(result);
});
let instanceData = (JSON.parse(departmentData)).feed.entry;
// Pushes to an array so I can sum up all the iterations
counters.push(instanceData.length);
Object.keys(instanceData).forEach(function (key) {
let val = instanceData[key];
let processId = val.id[0];
let nextURL = //url with + id";
request({url: nextURL},
function (error, response, body) {
//Convert the XML into JSON
parseString(response.body, function (err, result) {
departmentData = JSON.stringify(result);
});
let instanceData = (JSON.parse(departmentData)).feed.entry;
let val = instanceData[0].id[0];
let todoLink = //url with + id";
request({url: todoLink},
function (error, response, body, callback) {
//Convert the XML into JSON
parseString(response.body, function (err, result) {
departmentData = JSON.stringify(result);
});
let instanceEntry = (JSON.parse(departmentData)).feed.entry;
if (typeof instanceEntry !== 'undefined' && instanceEntry) {
let finalLink = //url with + id";
request({url: finalLink},
function (error, response, body) {
//Convert the XML into JSON
parseString(response.body, function (err, result) {
departmentData = JSON.stringify(result);
});
let instanceEntry = (JSON.parse(departmentData)).feed.entry;
if (typeof instanceEntry !== 'undefined' && instanceEntry) {
upNext = {
// some data
};
let data = {
// some data
}
processInstances.push(data);
counter++;
}
});
}
else {
upNext = 'null';
let data = {
// some data
}
counter++;
}
console.log(counter);
let counterSum = counters.reduce(function (a, b) {
return a + b;
}, 0);
if ((counter === counterSum)) {
res.send(projects);
}
});
});
});
});
let data = {
processName: processName,
id: id,
instances: processInstances
};
projects.push(data);
});
var UfcAPI = require('ufc-api');
var ufc = new UfcAPI({
version: '3'
});
const fighterId = [];
function getFighterId() {
ufc.fighters(function(err, res) {
for (let i = 0; i < res.body.length; i++) {
fighterId.push(res.body[i].id);
}
// console.log(fighterId);
})
};
function allFighters() {
for (let j = 0; j < fighterId.length; j++) {
request("http://ufc-data-api.ufc.com/api/v3/us/fighters/" + fighterId[j] + ".json", function(error, response, body) {
if (!error && response.statusCode === 200) {
console.log(JSON.parse(body));
}
})
}
};
The first function getFighterId() will look through the json for all fighters on the UFC roster. I capture all Ids of each fighter and push it to the fighterId array. Then i want to do a loop querying fighters with their id to get more detailed info on each fighter.
I understand that I need the array to completely get filled then run my second function. I found info on promises and Async but super confused on how to implement it.
Thanks
var UfcAPI = require('ufc-api');
var ufc = new UfcAPI({
version: '3'
});
function allFighters(fighterIds) {
fighterIds.forEach(fighterId => {
request("http://ufc-data-api.ufc.com/api/v3/us/fighters/" + fighterId + ".json", function(error, response, body) {
if (!error && response.statusCode === 200) {
console.log(JSON.parse(body));
}
})
});
};
function getFighterId(){
return new Promise((resolve,reject)=>{
ufc.fighters((err,res)=>{
if(err) reject(err);
else resolve(res.body)
})
})
}
getFighterId()
.then((ufcData)=>ufcData.map((u)=>u.id))
.then((data)=>allFighters(data))
.catch((err)=>console.log(err));
Please try with this one I have implemented with Promises. it can be implemented in many more ways. Let me know if it helps you.
I'm messing with SteamAPI in order to learn some NodeJS. Right now I'm trying to get games's info after an initial request to get the player's profile, once I have the games IDs stored in an array. The point is that I don't know how to "return" an array AFTER the whole ID array is iterated and all results has come from the server.
function getThumbs(game) {
return rq(
'http://store.steampowered.com/api/appdetails?appids=' + game,
{json: true},
function (error, response, bd) {
if(response.statusCode === 200 && bd[game].data) {
return bd[game].data.screenshots;
}
});
}
function getGamesThumbnails(games) {
var deferred = $q.defer(),
queue = [];
for (var y = 0; y < games.length; y++) {
var game = games[y];
var thumbs = getThumbs(game);
queue.push(thumbs);
}
$q.all(queue).then(
function (data) {
deferred.resolve(data);
},
function (err) {
deferred.reject(err)
}
);
return deferred.promise;
}
app.get('/blog',function(client_req,client_res){
rq('http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=' + key + '&steamid=blablabla&format=json', function (error, response, body) {
var data = JSON.parse(body);
var games = data.response.games.map(function (game) {
return game.appid;
});
getGamesThumbnails(games).then(function (data) {
console.log(data)
})
});
});
Basically, you should use a callback, because like you are doing in getThumbsyou are returning the object, while you should return the value bd[game].data.screenshots;
function getThumbs(game, cb) {
return rq(
'http://store.steampowered.com/api/appdetails?appids=' + game,
{json: true},
function (error, response, bd) {
if(response.statusCode === 200 && bd[game].data) {
cb(null, bd[game].data.screenshots);
}
});
}
function getGamesThumbnails(games) {
var deferred = $q.defer(),
queue = [];
for (var y = 0; y < games.length; y++) {
var game = games[y];
getThumbs(game, function(err, value) {
queue.push(value);
});
}
$q.all(queue).then(
function (data) {
deferred.resolve(data);
},
function (err) {
deferred.reject(err)
}
);
return deferred.promise;
}
And plust to return the response to the client you have to use the client_res.send(VALUE)
so the bottom part would become like this:
app.get('/blog',function(client_req,client_res){
rq('http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=' + key + '&steamid=blablabla&format=json', function (error, response, body) {
var data = JSON.parse(body);
var games = data.response.games.map(function (game) {
return game.appid;
});
getGamesThumbnails(games).then(function (data) {
client_res.send(data);
console.log(data)
})
});
});
Your getThumbs() function does not return a promise. $q.all only works on an array containing promises, whereas rq uses callbacks.
Try this:
function getThumbs(game) {
var deferred = $q.defer(),
rq(
'http://store.steampowered.com/api/appdetails?appids=' + game,
{json: true},
function (error, response, bd) {
if(response.statusCode === 200 && bd[game].data) {
deferred.resolve(bd[game].data.screenshots);
}
});
return deferred.promise;
}
Thank you both!
I tried Yuri's approach, but $q.all it doesn't seem to resolve the array of promises (nothing happens after the last request from getThumbs())
for (var y = 0; y < games.length; y++) {
var game = games[y];
var thumbs = getThumbs(game);
queue.push(thumbs);
}
$q.all(queue).then(
function (data) {
console.log(data)
deferred.resolve(data);
},
function (err) {
deferred.reject(err)
}
);