This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 8 years ago.
BACKGROUND
I have made a small site to run a tipping competition. I am using chartjs to draw garphs. I have made a function (createCjsDonutDataWinnerPickData) to format the data for the graph in to a format suitable for chartjs.
THE PROBLEM
For some reason the function (createCjsDonutDataWinnerPickData) is returning undefined. I think it is due to the "return" returning before the function has finished processing.
I am calling the function in two places. The first is a test before the page is rendered and the second time is when rendering the page.
Extract from page rendering function
Point.findOne({user:req.user.id, competition:comp.id, fixture:fixture.id}).exec(function (err, points){
if (err) {console.log('ERR: fixtures pick page on COMP lookup')}
else {
console.log('TEST FUNCTION');
console.log(createCjsDonutDataWinnerPickData(fixture._id,comp._id,createIdLookup(teams)));
res.render('fixturePick.ejs', {
user : req.user, // get the user out of session and pass to template
fixture: fixture,
pick: pick,
teams: createIdLookup(teams),
draw: draw,
round: round,
competition: comp,
points: points,
gWinnerPickGraph: JSON.stringify( createCjsDonutDataWinnerPickData(fixture._id,comp._id,createIdLookup(teams)) ),
successMsg: req.flash('successMsg'),
dangerMsg: req.flash('dangerMsg'),
warningMsg: req.flash('warningMsg')
});
}
});
The function createCjsDonutDataWinnerPickData
function createCjsDonutDataWinnerPickData(fixtureID,competitionID,teamLookup){
var Statistic = require('../app/models/statistic');
var Fixture = require('../app/models/fixture');
var async = require('async');
try {
async.waterfall([
function(cb_ReturnData){
var chartData =[];
Statistic.findOne({fixture:fixtureID, competition:competitionID,type:'winnerPickNumber'}).populate('fixture').exec(function (err,statData){
if (err) {console.log('ERROR in preparing data for graph');throw (err)}
else {
async.each(statData.data, function(dataPoint,cb_PrepData){
var sliceData = {};
if (statData.fixture.homeTeam._id == dataPoint.teamID){
//console.log('Found data for home team')
sliceData = {value: dataPoint.number, color:"rgba(151,187,205,0.5)", highlight: "rgba(151,187,205,0.75)", label:teamLookup[dataPoint.teamID].name };
}
else
{
//console.log('Found data for away team')
sliceData = {value: dataPoint.number, color:"rgba(220,220,220,0.5)", highlight: "rgba(220,220,220,0.75)", label:teamLookup[dataPoint.teamID].name };
}
//console.log('Pusihgin slice data to data')
chartData.push(sliceData);
cb_PrepData();
}, function(err){
if (err) {
console.log('ERROR in creating data for WinnerPickGraph');
cb_ReturnData(err);
}
else {
//console.log('CHART DATA IN INNER ASYNC');
//console.log(chartData);
cb_ReturnData(null, chartData);
}
});
}
});
}
],function(err,chartData){
if (err) {console.log('ERROR in preparing return data')}
else {
console.log('HERE IS THE FINAL RETURN');
console.log(chartData);
return (chartData);
}
});
}
catch (err){
//probably end up here because there is no points History Data
console.log('threw erro returning undefined');
return undefined;
}
}
Console output
GET /fixturePick?competition=542a5ffa736e3e35532f2d24&fixture=542c9ae12367c9209a739150 302 5.532 ms - 58
PATH: /fixturePick?competition=542a5ffa736e3e35532f2d24&fixture=542c9ae12367c9209a739150
GET / 200 11.291 ms - 1515
GET /images/grassBackground.jpg 200 5.533 ms - 145369
POST /login 302 459.431 ms - 228
TEST FUNCTION
undefined
GET /fixturePick?competition=542a5ffa736e3e35532f2d24&fixture=542c9ae12367c9209a739150 200 1609.303 ms - 4984
GET /images/team/logo/sm/53fc6399b918a6b661d423b4.png 200 2.720 ms - 15747
HERE IS THE FINAL RETURN
[ { value: 1,
color: 'rgba(220,220,220,0.5)',
highlight: 'rgba(220,220,220,0.75)',
label: 'Melbourne Victory' },
{ value: 2,
color: 'rgba(220,220,220,0.5)',
highlight: 'rgba(220,220,220,0.75)',
label: 'Western Sydney Wanderers' } ]
HERE IS THE FINAL RETURN
[ { value: 1,
color: 'rgba(220,220,220,0.5)',
highlight: 'rgba(220,220,220,0.75)',
label: 'Melbourne Victory' },
{ value: 2,
color: 'rgba(220,220,220,0.5)',
highlight: 'rgba(220,220,220,0.75)',
label: 'Western Sydney Wanderers' } ]
GET /images/team/logo/sm/53fc6399b918a6b661d423b5.png 200 2.368 ms - 13144
GET /images/none.png 200 1.857 ms - 195
I may have overcomplicated this with async.foreach and async.waterfall but I was running out of ideas as to why the function keeps returning undefined.
createCjsDonutDataWinnerPickData will return undefined always because it's asynchronous. To get a handle on results, you should either pass it a callback or return a promise.
Here are some examples of the approaches:
// callback
createCjsDonutDataWinnerPickData(fixture._id,comp._id,createIdLookup(teams), function(err, data) {
// do something with err and data now
});
// promise (es6-promise)
createCjsDonutDataWinnerPickData(fixture._id,comp._id,createIdLookup(teams)).then(function(data) {
// got some data, do something
}).catch(function(err) {
// deal with the error
});
Latter approach is possible with es6-promise. bluebird works well too.
Related
I use the following code to make a saved search request. I get the output as JSON_ROWS format. I would like to get the result in JSON or XML format. Where i can specify the expected output format in the request ?
// The saved search created earlier
var searchName = "test";
// Retrieve the saved search collection
var mySavedSearches = service.savedSearches();
mySavedSearches.init(service, {app:"-"});
mySavedSearches.fetch(function(err, mySavedSearches) {
// Retrieve the saved search that was created earlier
var mySavedSearch = mySavedSearches.item(searchName);
// Run the saved search and poll for completion
mySavedSearch.dispatch({"args.splunkCMD": searchCommand}, function(err, job) {
// Display the job's search ID
sails.log("SplunkController::executeGenericSplunkRequest() Job SID: ", job.sid);
// Poll the status of the search job
job.track({
period: 200
}, {
done: function(job) {
sails.log("SplunkController::executeGenericSplunkRequest() Splunk request completed!");
// Get 10 results and print them
job.results({
count: 10
}, function(err, results, job) {
sails.log("SplunkController::executeGenericSplunkRequest() results = " +JSON.stringify(results, null, 2));
callback(results);
});
},
failed: function(job) {
console.log("Job failed")
},
error: function(err) {
done(err);
}
});
});
});
I have a MySQL table with a url column and I want to get a list of every url value in the table. Structure:
In PHP using Laravel I could have done something like this to get an array of every url column value:
$boards = Board::all()->lists('url');
However, I cannot figure out how to do this using Sails JS. I've noticed that find() without any argument gets every row in the database but I can't find anything in the documentation that would allow me to either get a list of the values for the url column or a way to iterate over the returned collection.
I've tried this:
var boards = Board.find().exec(function(error, _boards) {
if(error)
{
return response.negotiate(error);
}
return _boards;
});
However, I can't actually seem to iterate over the returned data.
Any thoughts?
EDIT:
Full HomepageController.js:
module.exports = {
index: function (request, response) {
var data = {
currentDate: (new Date()).toString(),
boards: Board.query('SELECT url FROM board', function (error, results) {
if (error) {
return response.negotiate(error);
}
return results;
})
};
return response.view('homepage', data);
}
};
EDIT: Working Code:
module.exports = {
index: function (request, response) {
Board.query('SELECT url FROM board', function(error, results) {
if(error)
{
return response.negotiate(error);
}
return response.view('homepage', { currentDate: (new Date()).toString(), boards: results });
});
}
};
You can use a raw .query() to select individual columns:
Board.query('SELECT url FROM board', function (err, results) { ... });
However, keep in mind that your data will still look like this:
[{url: 'row 1 value'}, {url: 'row 2 value'}, ...]
I'm pretty new to Mongodb and have so far successfully used Find, Insert, Update methods. However, with Delete function I am not able to access WriteResult
Insert (Works)
productCollection.insert(newProduct, function (err, result) {
callBack(err, { message: result["insertedCount"] + ' product created successfully.' });
});
Find (Works)
productCollection.find({}).toArray(function (err, docs) {
callBack(err, { product: docs });
});
Delete (Has Issues)
productCollection.remove({ id: pId }, { justOne: 1 }, function (err, result) {
callBack(err, { message: result});
});
Here when I return {message: result} I get
{
"message": {
"ok": 1,
"n": 0
}
}
But I want to actually read "n" from Result to show no of documents deleted
Tried following
{ message: result["n"] }
{ message: result["nRemoved"] }
But in both cases it returns empty object {}.
According to the 2.0 version of Node.js MongoDB Driver API, remove() method is deprecated, you can use removeOne() method instead:
http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#remove
In order to receive number of documents that were removed, you need to use safe mode to ensure removal of documents. To do this, specify write concern by passing {w: 1} to removeOne() function:
productCollection.removeOne({ _id: pId }, { w:1 }, function(err, r) {
// number of records removed: r.result.n
callBack(err, { message: r });
});
Hope this helps.
Thanks Yelizaveta for pointing out the deprecated method. However in my case, following worked
productCollection.removeOne({ id: pId }, { w: 1 }, function (err, r) {
callBack(err, { message: r.result["n"]});
});
I could not get r.result.n working instead r.result["n"] worked, which I don't understand.
UPDATE
Got it to work, but I still think there's a problem. I only get the correct return data if I set the setTimeout timer to be really long, like 2000. If I leave it at 200 then the callback function executes with empty data because the API call hasn't been returned yet.
I've updated the code below.
Setup:
I'm sending a get value from the front end via AJAX (jQuery) and then using that value to call the Foursqaure API to get a list of relevant venues back.
This is working "fine" except that the order of events get screwy. When I insert the GET value into the parameters for the function to evaluate, I'm getting a return value that I'm not asking for, which then causes the template to render on the front end before the other return value from my function – the one I want – is given.
And actually I don't think it's actually being returned. Just logged to the console.
Question:
How can I return the filtered list of JSON objects at the end of the initGetVenues function in places.js via AJAX to the front end?
Context:
I'm using this package to connect to Foursquare:
https://npmjs.org/package/foursquarevenues
AJAX call on front end
$("#search-items").submit(function() {
var placeQuery = $("#search-input").val();
$.ajax({
url: '/return/places/',
data: {"passPlaceQuery": placeQuery},
type: 'get',
dataType: 'html',
success: function(data) {
$("#search-results-list").html(data);
},
});
return false;
});
index.js [UPDATED]
returnPlaces: function(req, res) {
if (req.headers['x-requested-with'] === 'XMLHttpRequest') {
console.log("I've started routing");
return places.findVenue({
ll: "38.214986,-85.637054",
radius: 32186,
query: req.query.passPlaceQuery,
intent: "browse",
categoryId: "4bf58dd8d48988d1e0931735"
}, function(err, data) {
console.log("Venue callback");
if (err) {
res.send(500);
}
console.log("Attempting render: " + data);
return res.render("place-results", {
layout: false,
foundPlaces: data
});
});
} else {
return res.redirect("/");
}
}
places.js [UPDATED]
(function() {
var foursquare, initGetVenues;
foursquare = (require('foursquarevenues'))('SECRET', 'SECRET');
module.exports = {
findVenue: initGetVenues = function(criteria, callback) {
var jsonUniquePlaces;
jsonUniquePlaces = [];
foursquare.getVenues(criteria, function(error, venues) {
var i, objUniquePlace, range, uniquePlaces, venueName;
if (!error) {
range = Object.keys(venues.response.venues).length;
uniquePlaces = [];
i = 0;
while (i < range) {
venueName = venues.response.venues[i].name;
if (!(uniquePlaces.indexOf(venueName) > -1)) {
uniquePlaces.push(venueName);
}
i++;
}
i = 0;
while (i < uniquePlaces.length) {
objUniquePlace = {
place: uniquePlaces[i]
};
jsonUniquePlaces.push(objUniquePlace);
i++;
}
jsonUniquePlaces = JSON.stringify(jsonUniquePlaces);
return jsonUniquePlaces;
}
});
return setTimeout((function() {
return callback(null, jsonUniquePlaces);
}), 200);
}
};
}).call(this);
When setTimeout is 2000 I get:
| I've started routing
| [{"place":"Quills Coffee"},{"place":"Quills Coffe"},{"place":"Quill's Coffee"}]
| Venue callback
| Attempting render: [{"place":"Quills Coffee"},{"place":"Quills Coffe"},{"place":"Quill's Coffee"}]
| GET /return/places/?passPlaceQuery=quills 200 2009ms - 150
When setTimeout is 200 I get:
| I've started routing
| Venue callback
| Attempting render:
| GET /return/places/?passPlaceQuery=quills 200 210ms - 11
| [{"place":"Quills Coffee"},{"place":"Quills Coffe"},{"place":"Quill's Coffee"}]
You can't just return your value from inside findVenue. The call to foursquare.getVenues is asynchronous. So when the node engine gets to the function call foursquare.getVenues(opt, callback) it simply starts the operation and continues executing any further statements, then index.js continues executing, then you render a response... Finally some time later, the foursquare.getVenues code invokes its callback (presumably when it's done talking to the foursquare API.)
You need to rewrite places.findVenue to take a callback argument. When you call places.findVenue() you will pass it a function to execute as a callback. That is when you're supposed to send the response.
Here is a simplified example that you can hopefully extend:
function findVenue(opt, callback){
setTimeout(function(){
console.log('Done with foursquare.getVenues, calling back to routing function')
callback(null, opt);
// you passs this callback function to setTimeout. it executes it in 200ms
// by convention, pass a null error object if successful
}
,200);
};
app.get('/return/places', function(req, res){
console.log('routing function start');
findVenue({
lat:40,
lng: 70,
query: 'foo'
}, function(err, data){
console.log('findVenue callback');
if(err){ return res.send(500) };
res.render('template', {foo: data});
});
});
For some reason, I can't seem to get vows.js sub-topics working in my real test-suite, but they work fine in a example file... can you spot my problem?
This works:
vows.describe('An Education in Vows').addBatch({
'when I write an asynchronous topic': {
topic: function() {
var that = this;
setTimeout(function() {
that.callback(true);
}, 100);
},
'it passes': function (topic) {
assert.equal(topic, true);
},
'and it has an asynchronous sub-topic': {
topic: function() {
var that = this;
setTimeout(function() {
that.callback(true);
}, 100);
},
'it also passes': function (topic) {
assert.equal(topic, true);
}
}
}
}).run();
When I run this via:
node tests/learning-vows.js
I get:
·
·
✓ OK » 2 honored (0.207s)
This Doesn't work:
I have a file ./tests/smoke.js
vows.describe('Registration & Authentication').addBatch({
'when a user registers with a valid username and password': {
topic: function () {
client.register({
username: validusername,
password: validpassword
}, this.callback);
},
'we return status 200 OK': function (data, response) {
assert.equal(200, response.statusCode);
},
'simple sub-topic': {
topic: true,
'should work': function(topic) {
assert.equal(true, topic);
}
},
}
}).run()
When I execute this via:
node tests/smoke.js
I get:
·
✗ Errored » 1 honored ∙ 1 errored
Note that in the second example, without the sub topic I get:
·
✓ OK » 1 honored (0.100s)
Vows is using node's convention for callbacks (see : http://nodemanual.org/latest/nodejs_dev_guide/working_with_callbacks.html), it assumes that first parameter on callback is an error object.
So when you send data as first parameter your telling vows that an error happened in client.register. It prevents vows from evaluating sub-topic. Sub-topic is marked errored but assertion succeeded and current topic is marked as honored.
It is really not trivial to guess that from output. Moreover vows behavior is not consistent, try replacing true to 0 and then '0' as callback parameter in your first test and you will see two other results.
Here is a working exemple :
var vows = require('vows'), assert = require('assert');
var client = {
register: function(obj,callback){
callback(null, obj, {statusCode:200});
}
};
vows.describe('Registration & Authentication').addBatch({
'when a user registers with a valid username and password': {
topic: function () {
client.register({
username: 'validusername',
password: 'validpassword'
}, this.callback);
},
'we return status 200 OK': function (err, data, response) {
assert.equal(response.statusCode, 200);
},
'simple sub-topic': {
topic: true,
'should work': function(topic) {
assert.equal(true, topic);
}
}
}
}).export(module)