I have the following code. The array prices does not seem to be pushed to the prices array despite successfully retrieving the ustock.unitprice.
getLatestMarketPrices: function(username, callback) {
var prices = [];
db.find('portfolio', {user: username}, function(err, stocks) {
for(var i = 0; i < stocks.length; i++) {
module.exports.getQuote(stocks[i].stock, function(err, ustock) {
console.log(ustock.unitprice); // Retrieves 1.092
prices.push(ustock.unitprice); // Should push to prices array?
});
}
console.log(prices); // Prices is still [] despite earlier push.
callback(null, prices);
});
},
Is this a scoping issue? I'm not really sure why prices is not pushed to.
Thanks very much.
If you know jquery, you could try deferred object
getLatestMarketPrices: function(username, callback) {
var prices = [];
var defer = $.Deferred();
//Attach a handler to be called when the deferred object is resolved
defer.done(function(){
console.log(prices);
callback(null, prices);
});
db.find('portfolio', {user: username}, function(err, stocks) {
for(var i = 0; i < stocks.length; i++) {
module.exports.getQuote(stocks[i].stock, function(err, ustock) {
console.log(ustock.unitprice); // Retrieves 1.092
prices.push(ustock.unitprice); // Should push to prices array?
//resolve when we retrieve all
if (prices.length == stocks.length){
defer.resolve();
}
});
}
});
},
Update: or don't need deferred object at all:
getLatestMarketPrices: function(username, callback) {
var prices = [];
db.find('portfolio', {user: username}, function(err, stocks) {
for(var i = 0; i < stocks.length; i++) {
module.exports.getQuote(stocks[i].stock, function(err, ustock) {
console.log(ustock.unitprice); // Retrieves 1.092
prices.push(ustock.unitprice); // Should push to prices array?
//callback only when we receive all
if (prices.length == stocks.length){
console.log(prices);
callback(null, prices);
}
});
}
});
},
Related
Working on an mobile cordova/angular project. Below is a simple service call:
this.getSomeData = function (businessId) {
var deferred = $q.defer();
var query = "SELECT * FROM Stuff";
$cordovaSQLite.execute(db, query).then(function (res) {
deferred.resolve(res.rows);
}, function (err) {
deferred.reject(err);
});
return deferred.promise;
};
The issue is simple:
for (var k = 0; k < count; k++) {
myService.getSomeData($scope.model.stuff[k].id, k).then(function (data) {
// whatever
}
);
getSomeData is async, so by the time it returns, the k of the for cycle is far from correct.
I thought of passing k to the service method as a parameter:
for (var k = 0; k < count; k++) {
myService.getSomeData($scope.model.stuff[k].id, k).then(function (data) {
// whatever
}
);
And change the service method accordingly:
this.getSomeData = function (id, index) {
var deferred = $q.defer();
var query = "SELECT * FROM Stuff";
$cordovaSQLite.execute(db, query).then(function (res) {
deferred.resolve(res.rows, index);
}, function (err) {
deferred.reject(err);
});
return deferred.promise;
};
But that second parameter is ignored and is always undefined.
How to overcome this?
It sounds like you're running into a problem called "closure over the loop variable" and this issue is discussed in detail here:
JavaScript closure inside loops – simple practical example
In your case, however, the clean solution is to combine Array#map with $q.all():
$q.all($scope.model.visits.map(function (stuff) {
return myService.getSomeData(stuff.id);
})).then(function (results) {
// results is an array of the results of all the calls to getSomeData() in the correct order
});
Also, as Bergi points out, avoid the deferred antipattern:
this.getSomeData = function (id) {
var query = "SELECT * FROM Stuff";
return $cordovaSQLite.execute(db, query).then(function (res) {
return res.rows;
});
};
This is how I got it working. I tried using #JLRishe's suggestion but it wouldn't work. Turns out, I managed to pass more than one parameter through to the service method and back to the controller as well (by building an object than contains as many parameters I need).
myService.getSomeData().then(
function (stuff) {
// whatever
}
).then(function () {
for (var i = 0; i < $scope.model.stuff.length; i++) {
// HERE I SEND TWO PARAMETERS TO THE SERVICE METHOD
myService.getSomeMoreData($scope.model.stuff[i].id, i).then(
function (data) {
// whatever
}
);
}
});
this.getSomeMoreData = function (id, index) {
var deferred = $q.defer();
var query = "SELECT * FROM stuff";
$cordovaSQLite.execute(db, query).then(function (res) {
var moreStuff = [];
for (var i = 0; i < res.rows.length; i++) {
var junk = res.rows.item(i);
moreStuff.push(junk);
}
// HERE I RESOLVE AN OBJECT INSTEAD OF TWO PARAMETERS
deferred.resolve({
moreStuff: moreStuff,
index: index
});
}, function (err) {
deferred.reject(err);
});
return deferred.promise;
};
I have one mongojs query such as:
db.mapping.find(
{ "provider":req.params.provider, "perId":mongojs.ObjectId(req.params.perId) },
function(err, que){
if(err)
res.send(err);
else if(que)
{
totalVideoList = [];
for (var i=0; i < que.length; i++)
{
myid = que[i].providerId;
db.ABC.find({}, function(err, que1){
if(err)
res.send(err);
var x = {};
for (var j=0; j < que1.length; j++)
{
searching(que1[j]);
}
videoList = [];
getVideo(requiredDocument);
totalVideoList = totalVideoList.concat(videoList);
});
}
res.json(totalVideoList);
}
else
res.json([]);
});
Currently I am always getting [] (empty array) as my response. The problem is due asynchronous nature of callback function of mongojs. Before the expected output comes in "totalVideoList" variable, it responds us with totalVideoList = [].
I don't know how to use async.each() here. Please help me to tackle this problem.
Assuming using async
db.mapping.find(
{ "provider":req.params.provider, "perId":mongojs.ObjectId(req.params.perId) },
function(err, que){
if(err)
res.send(err);
else if(que)
{
totalVideoList = [];
async.each(que, function(item, callback){
myid = item.providerId;
db.ABC.find({}, function(err, item){
if(err)
return callback(err);
var x = {};
for (var j=0; j < item.length; j++)
{
searching(item[j]);
}
videoList = [];
getVideo(requiredDocument);
totalVideoList = totalVideoList.concat(videoList);
callback(null);
});
}, function(asyncErr){
if(asyncErr)
return resp.json(asyncErr);
resp.json(totalVideoList);
});
}
else
res.json([]);
});
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)
}
);
I keep receiving a code 141 error success/error was not called. I am running another function getCinemasInLocation which returns a JSON like: {result: [result1, result2]}. I want to iterate over this array and run a query each time the loop runs and all the results to an array. That is, the results of all oteration will be in an array. Am I doing it right?
//This function uses getCinemasInLocation to retrieve the movie objects that are showing in the cinemas
Parse.Cloud.define("getMovieIdsInCinemas", function(request, response) {
var cinemasInLocaton = [];
var theLocation = request.params.theLocation;
cinemasInLocation = Parse.Cloud.run("getCinemasInLocation", {theLocation: theLocation});
for (i = 0; i < cinemasInLocation.length; i++){
var query = new Parse.Query("showing");
var movieIds = [];
query.equalTo("cinema", {
__type: "Pointer",
className: "Cinema",
objectId: cinemasInLocation[i]
});
query.find({
success: function(results) {
for (var i = 0; i < results.length; i++) {
movieIds.push(results[i].get("movie"));
}
response.success(movieIds);
},
error: function() {
response.error("movie lookup failed 2");
}
});
}
});
This is the getCinemasInLocation that does not work
function getCinemasInLocation(theLocation) {
// some code
//var result = ["xiUXXYFhAl","Yanh9iDykk"];
//return result;
var result = new Parse.Promise();
var query = new Parse.Query("Cinema");
query.equalTo("Location", theLocation);
query.find({
success: function(objects) {
var cinemas = [];
for (var i = 0; i < objects.length; i++) {
var cinema = objects[i];
cinemas.push(cinema.id);
}
result.resolve(cinemas);
},
error: function(error) {
result.reject(error);
}
});
return result;
}
Parse.Cloud.run doesn't return an array. It returns a Promise. So, create a normal javascript function in the same file: getCinemasInLocation()
As #Delhi said, you can only call response.success() or response.error() once. So, don't put them in a loop.
Use Promises on parallel. So, let's use the loop of Underscore instead of the normal FOR loop. You can start multiple operations at once, and use Parse.Promise.when to create a new promise that will be resolved when all of its input promises is resolved. You can read more about this in the documentation: https://www.parse.com/docs/js_guide#promises-parallel
var _ = require('underscore');
function getCinemasInLocation(theLocation) {
// some code
var result = [id1, id2];
return result;
}
// This function returns the array of movieIds of a cinema
function getMovieIdsInCinema(cinemaId) {
var result = new Parse.Promise();
var query = new Parse.Query("showing");
query.equalTo("cinema", {
__type: "Pointer",
className: "Cinema",
objectId: cinemaId
});
query.find({
success: function(objects) {
var movieIds = [];
for (var i = 0; i < objects.length; i++) {
var movie = objects[i].get("movie");
movieIds.push(movie.id);
}
result.resolve(movieIds);
},
error: function(error) {
result.reject(error);
}
});
return result;
}
Parse.Cloud.define("getMovieIdsInCinemas", function(request, response) {
var cinemasInLocation = [];
var theLocation = request.params.theLocation;
cinemasInLocation = getCinemasInLocation(theLocation);
var promises = [];
_.each(cinemasInLocation, function(cinemaId) {
promises.push(getMovieIdsInCinema(cinemaId));
});
Parse.Promise.when(promises).then(
function() {
var result = [];
_.each(arguments, function(object) {
result.push(object); // each object is an array of movieIds
});
response.success(result); // return array of arrays
},
function(error) {
response.error(error);
}
);
});
I want to retrieve the unique id from the database and store them into the object (userslist).
Why can not I retrieve data into an object?
Object userslist is empty {}
I have this function in Node.js:
getOnlineUsers: function()
{
var userslist = {};
client.query(
'SELECT DISTINCT Aid FROM online',
function selectCb(err, results, fields) {
if (err) {
throw err;
}
if (results.length > 0) {
for (var i = 0; i < results.length; i++) {
var reader = results[i];
userslist[reader['Aid']] = {
fbid: reader['Aid']
}
}
}
}
);
return userslist;
}
Here's one example of what was discussed in the comments above.
Pass a callback to getOnlineUsers...
my_obj.getOnlineUsers(function(u_list) {
/* do something with the user list */
});
And have getOnlineUsers receive the callback argument, and invoke it...
// receive the callback----v
getOnlineUsers: function(cb_func) {
client.query(
'SELECT DISTINCT Aid FROM online',
function selectCb(err, results, fields) {
if (err) {
throw err;
}
var userslist = {}
if (results.length > 0) {
for (var i = 0; i < results.length; i++) {
var reader = results[i];
userslist[reader['Aid']] = {
fbid: reader['Aid']
}
}
}
cb_func(userslist); // <-- invoke the callback, passing the data
});
}
This is one approach, but basically illustrates that the continuation of the flow of code needs to take place in the callback passed to client.query.