Reputation: 1
I'm trying to learn Node.js via Express.js framework. Currently I need to make an API call to get some data usefull for my app.
The API call is made with Request middleware, but when I'm out of the request my variable become undefined ... Let me show you :
var request = require('request');
var apiKey = "FOOFOO-FOOFOO-FOO-FOO-FOOFOO-FOOFOO";
var characters = [];
var gw2data;
var i = 0;
module.exports.account = function() {
request('https://api.guildwars2.com/v2/characters/?access_token=' + apiKey, function (error, response, body) {
gw2data = JSON.parse(body);
console.log('out request ' + gw2data); // {name1, name2 ...}
for (i; i < gw2data.length; i++) {
getCharacInfo(gw2data[i], i);
}
});
console.log('out request ' + characters); // undefined
return characters;
};
function getCharacInfo (name, position) {
request('https://api.guildwars2.com/v2/characters/' + name + '/?access_token=' + apiKey, function (error, response, body) {
if (!error && response.statusCode == 200) {
characters[position] = JSON.parse(body);
}
});
}
I don't understand why the gw2data variable become undefined when I go out of the request ... Someone can explain me ?
EDIT : I come to you because my problem has changed, I now need to make an API call loop in my first API call, same async problem I guess.
The previous code has evoluate with previous answers :
module.exports.account = function(cb) {
var request = require('request');
var apiKey = "FOOFOO-FOOFOO-FOO-FOO-FOOFOO-FOOFOO";
var characters = [];
var i = 0;
request('https://api.guildwars2.com/v2/characters/?access_token=' + apiKey, function(err, res, body) {
var numCompletedCalls = 1;
for (i; i < JSON.parse(body).length; i++) {
if (numCompletedCalls == JSON.parse(body).length) {
try {
console.log(characters); // {} empty array
return cb(null, characters);
} catch (e) {
return cb(e);
}
}else {
getCharacInfo(JSON.parse(body)[i]);
}
numCompletedCalls++;
}
});
};
function getCharacInfo (name) {
request('https://api.guildwars2.com/v2/characters/' + name + '/?access_token=' + apiKey, function (err, res, body) {
if (!err) {
console.log(characters); // {data ...}
characters.push(JSON.parse(body));
}
});
}
And the call of this function :
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
var charactersInfos = require('../models/account');
charactersInfos.account(function(err, gw2data) {
if (err) {
console.error('request failed:', err.message);
} else {
res.render('index', {
persos : gw2data
});
}
});
});
The problem is, when I return the characters array, it's always an empty array, but when I check in my function getCharacInfo, the array contains the data ...
Upvotes: 0
Views: 892
Reputation: 82136
The reason gw2data
is undefined
in the second console.log
is because your logging it too early. request
is an asynchronous operation therefore it will return immediately...however, that doesn't mean the callback will.
So basically what your doing is logging gw2data
before it's actually been set. When dealing with asynchronous operations the best approach is to generally make your own method asynchronous as well which can be accomplished in a couple of ways - the simplest being having your function accept a callback which expects the data in an asynchronous way e.g.
module.exports.account = function(cb) {
request(..., function(err, res, body) {
// return an error if `request` fails
if (err) return cb(err);
try {
return cb(null, JSON.parse(body));
} catch (e) {
// return an error if `JSON.parse` fails
return cb(e);
}
});
}
...
var myModule = require('mymodule');
myModule.account(function(err, g2wdata) {
if (err) {
console.error('request failed', err.message);
} else {
console.log('out request', g2wdata);
}
});
Based on your syntax I'm assuming you aren't working with ES6, worth looking at this (particularly if your just starting to learn). With built-in promises & also async-await support coming relatively soon this sort of stuff becomes relatively straightforward (at least at the calling end) e.g.
export default class MyModule {
account() {
// return a promise to the caller
return new Promise((resolve, reject) => {
// invoke request the same way but this time resolve/reject the promise
request(..., function(err, res, body) {
if (err) return reject(err);
try {
return resolve(JSON.parse(body));
} catch (e) {
return reject(e);
}
});
});
}
}
...
import myModule from 'mymodule';
// handle using promises
myModule.account()
.then(gw2data => console.log('out request', gw2data))
.catch(e => console.error('request failed', e));
// handle using ES7 async/await
// NOTE - self-executing function wrapper required for async support if using at top level,
// if using inside another function you can just mark that function as async instead
(async () => {
try {
const gw2data = await myModule.account();
console.log('out request', gw2data);
} catch (e) {
console.log('request failed', e);
}
})();
Finally, should you do decide to go down the Promise route, there are a couple of libraries out there that can polyfill Promise support into existing libraries. One example I can think of is Bluebird, which from experience I know works with request
. Combine that with ES6 you get a pretty neat developer experience.
Upvotes: 1
Reputation: 1398
The request API is asynchronous, as is pretty much all I/O in Node.js. Check out the Promise API which is IMO the simplest way to write async code. There are a few Promise adapters for request, or you could use something like superagent with promise support.
Upvotes: 0