I have an express app where I'm using ejs for rendering the views. For example, this is one of the views I have:
<a href="/admin/categories" class="list-group-item">
<span style="margin-right: 6px" class="glyphicon glyphicon-folder-close"></span>
Categories
<span class="badge categoriesCount"><%= catCount %></span> <!-- get data from mongo -->
</a>
and in my routes file, I have gotten the values from mongodb and tried passing them into the view like say:
router.get('/', (req, res) => {
let cat_count = 0,
prod_count = 0,
user_count = 0,
order_count = 0;
Category.count({}, (err, count) => {
if (!err) {
cat_count = count;
console.log('Cat count from db:\t' + count);
} else {
console.error('Error Fetching Categories Count:\t' + err);
}
});
Products.count({}, (err, count) => {
if (!err) {
prod_count = count;
console.log('Prod count from db:\t' + count);
} else {
console.error('Error Fetching Products Count:\t' + err);
}
});
Users.count({}, (err, count) => {
if (!err) {
user_count = count;
console.log('User count from db:\t' + count);
} else {
console.error('Error Fetching Users Count:\t' + err);
}
});
Orders.count({}, (err, count) => {
if (!err) {
order_count = count;
console.log('Orders count from db:\t' + count);
} else {
console.error('Error Fetching Orders Count:\t' + err);
}
});
res.render('index', {
catCount: cat_count,
prodCount: prod_count,
userCount: user_count,
orderCount: order_count
});
});
where the catCount is an actual argument for the variable in my template. This does not work and I'm stuck with figuring this way out.
I have also tried using the DOM querySelector('className').innerHTML but that also doesn't work.
Which is the best way to send values to my template, I would prefer ejs.
Thanks.
Your code will not work because the values for your count variables will only get assigned after the callback runs. When dealing with callbacks, you have to note that the code in the callback function will not execute right away when you run the main function. What you have right now is something like the following:
// initialize variables
// execute Category.count({}) and call the function that you passed in when data is ready
// execute Products.count({}) and call the function that you passed in when data is ready
// execute Users.count({}) and call the function that you passed in when data is ready
// execute Orders.count({}) and call the function that you passed in when data is ready
// render
All these 5 lines of code will run within a split second and it will not wait until those callback functions are called. One way to solve it is to nest all the callbacks in one another:
router.get('/', (req, res) => {
let cat_count = 0,
prod_count = 0,
user_count = 0,
order_count = 0;
Category.count({}, (err, count) => {
if (!err) {
cat_count = count;
console.log('Cat count from db:\t' + count);
Products.count({}, (err, count) => {
if (!err) {
prod_count = count;
console.log('Prod count from db:\t' + count);
Users.count({}, (err, count) => {
if (!err) {
user_count = count;
console.log('User count from db:\t' + count);
Orders.count({}, (err, count) => {
if (!err) {
order_count = count;
console.log('Orders count from db:\t' + count);
res.render('index', {
catCount: cat_count,
prodCount: prod_count,
userCount: user_count,
orderCount: order_count
});
} else {
console.error('Error Fetching Orders Count:\t' + err);
}
});
} else {
console.error('Error Fetching Users Count:\t' + err);
}
});
} else {
console.error('Error Fetching Products Count:\t' + err);
}
});
} else {
console.error('Error Fetching Categories Count:\t' + err);
}
});
});
However, this is really messy and is known as the callback hell in JavaScript. This is where Promises comes in handy.
Here's what you can do:
router.get('/', (req, res) => {
let queries = [
Category.count({}),
Products.count({}),
Users.count({}),
Orders.count({})
];
Promise.all(queries)
.then(results => {
res.render('index', {
catCount: results[0],
prodCount: results[1],
userCount: results[2],
orderCount: results[3]
});
})
.catch(err => {
console.error('Error fetching data:', err)
});
});
For Mongoose, when you don't pass in a function as the second parameter, the value returned by the function call (e.g. Category.count({})) will be a Promise. We will put all of these unresolved Promises in an array and call Promise.all to resolve them. By using Promise.all, you are waiting until all of your count queries get executed before moving on to render your index view. In this case, the results of your Mongoose function calls will be stored in the results array in the order of your queries array.
Related
In an Express JS connected to a mySQL db, I am trying to get some data of an already defined route/ query:
// customers.model.js
CUSTOMERS.getAll = (result) => {
let query = "SELECT * FROM customers"
sql.query(query, (err, res) => {
if (err) {
console.log("error: ", err)
result(null, err)
return
}
result(null, res)
})
}
// customers.controller.js
// GET customers is a standalone route and should output all the customers when called.
const CUSTOMERS = require("../models/customers.model.js")
exports.findAll = (req, res) => {
return CUSTOMERS.getAll((err, data) => {
if (err)
res.status(500).send({
message: err.message ||
"Some error occurred while retrieving customers...",
})
else res.send(data)
})
}
In payments.controller.js I would firstly like to get all users so I can do something with the data:
// payments.controller.js
// GET payments is also a standalone route and should get the customers,
// do something with the data and output a calculation with the help of this data
const CUSTOMERS = require("../models/customers.model.js")
exports.calculateAll = (req, res) => {
const customers = CUSTOMERS.getAll((err, data) => {
console.log('this always has correct data', data)
if (err) return err
else return data
})
console.log('this is always undefined', customers)
...
res.send(whatEverCalculatedData)...
}
But that data here is always undefined.
What am I doing wrong in the above, and what's the correct way to call this route inside another route?
I know it has similarities with this question but I couldn't sort it out for my particular example.
It's due to your call which is asynchronous.
You must wait your data being ready before rendering the results.
Maybe you could to use Promises or async/await statements.
For example:
CUSTOMERS.getAll = async () => {
const query = "SELECT * FROM customers";
try {
return await sql.query(query);
} catch (e) {
console.log(`An error occurred while fetching customers: ${e.message}.`);
return null;
}
}
exports.calculateAll = async (req, res) => {
try {
const data = await CUSTOMERS.getAll();
res.send(whatEverCalculatedData);
} catch (e) {
res.send(`Something went wront: ${e.message}.`);
}
}
I have an array that I am trying to return but only return certain rows within that array if the column Free_Agent is populated (or I could do if Years_Kept is greater than or equal to 2).
Here is my code where I have been able to successfully return the values of the array by a console log. Everything I try when I either try to do a forEach or pull the filtered data via a filter view I have set up (363219995) will not work.
async function gsrun(cl){
const gsapi = google.sheets({version:'v4', auth: cl });
gsapi.spreadsheets.values.get({
spreadsheetId: "11e5nFk50pDztDLngwTSmossJaNXNAGOaLqaGDEwrbQM",
range: 'Keepers!C1:F',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const rows = res.data.values;
if (rows.length) {
rows.map((row) => {
console.log(`${row[0]}, ${row[1]}, ${row[2]}, ${row[3]}`);
});
} else {
console.log('No data found.');
}
})
};
Terminal Screenshot of mapped Array
Can anybody please help a newbie? I've been teaching myself and watching videos and reading everything I can, I just can't seem to grasp it.
You have to filter the values (rows) arrays to check if there is a value.
async function gsrun(cl){
const gsapi = google.sheets({version:'v4', auth: cl });
gsapi.spreadsheets.values.get({
spreadsheetId: "11e5nFk50pDztDLngwTSmossJaNXNAGOaLqaGDEwrbQM",
range: 'Keepers!C1:F',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const rows = res.data.values;
if (rows.length) {
const cells = rows.filter(cell => cell[3])
cells.map((cell) => {
console.log(`${cell[0]}, ${cell[1]}, ${cell[2]}, ${cell[3]}`);
});
} else {
console.log('No data found.');
}
})
};
Just adding to #ArthurCosta's answer, based on the second condition you provided.
async function gsrun(cl){
const gsapi = google.sheets({version:'v4', auth: cl });
gsapi.spreadsheets.values.get({
spreadsheetId: "11e5nFk50pDztDLngwTSmossJaNXNAGOaLqaGDEwrbQM",
range: 'Keepers!C1:F',
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const rows = res.data.values;
if (rows.length) {
const cells = rows.filter(cell => cell[3] || (parseInt(cell[2]) >= 2))
cells.map((cell) => {
console.log(`${cell[0]}, ${cell[1]}, ${cell[2]}, ${cell[3]}`);
});
} else {
console.log('No data found.');
}
})
};
I am trying to store API results into an array.
The data is displayed in console, but on pushing the data into an array, the array is still empty.
Here's the code:
app.post('/fetchFavoriteTweets/', verifyToken, function(req, res) {
var favorites = [];
dbConn.then( function (database) {
var dbo = database.db("twitter_search");
dbo.collection('users').findOne(
{ _id: ObjectId(req.userId) }, function(err, result) {
if(err) throw err;
if(!result.hasOwnProperty('favorite_tweets')) {
res.status(404).json({msg:'record not found'});
}
else {
result.favorite_tweets.forEach(function (tweet) {
T.get('statuses/show', {id: tweet.id}, function(err, data, response) {
if(!err){
favorites.push(data);
console.log(data); //this returns data
} else {
console.log(err);
}
});
});
console.log(favorites);
// res.status(200).json({msg:'success', data:favorites});
}
});
}).catch(function(e){console.log(e)})
});
It looks like you're defining the favorites array within the scope of the function callback. Try putting var favorites = []; above you app.post() call instead.
Also, keep in mind that it will only have a value after the callback is complete, so any synchronous code later down the line will only see the empty array value.
I've updated your code to get favorites by storing separately the promise and call it afterwards:
UPDATE
As you can see in the demo, i have 2x console.log at the bottom, the first one(C1) is contained in the promise favoritesPromise () and the second (C2) is after the promise.
Synchronous actions will never wait for asynchronus actions to take place, therefore in my example C2 will always be outputted before C1, even if console.log(1 ... ) is before console.log(2 ... ), they'll appear reversed in the console.
In the promise i added a setTimeout of 1ms to mock a request, it was all it took to achieve the current output. Another thing you can test is removing the setTimeout then output will change a bit, your promise becomes synchronus until it reaches resolve(favorites), that means favorites has all the data by now, but when resolve takes place, it becomes async, and in your console you will still see C2 first (but now with data) and C1 second.
In my earlier answer i tried to implement this reasoning to your code.
Keep it async folks!
var favorites = [];
var favoritesPromise = () => {
return new Promise((resolve, reject) => {
console.log('Retrieving data from the internet.');
// This timeout mocks your request to anything that is async or promie
setTimeout(() => {
console.log('Request done')
let resultFavorite_tweets = [{
id: 1,
name: 'a dog'
}, {
id: 2,
name: 'a cat'
}];
resultFavorite_tweets.forEach(item => {
favorites.push(item.name);
})
resolve(favorites);
// if you have an error use
// reject(err)
}, 1);
});
}
favoritesPromise().then(favList => {
console.log(1, 'this will always contain data from the internet, but will always be last', favList);
})
console.log(2, 'this will be empty (unless you remove setTimeout), but will always be first', favorites);
app.post('/fetchFavoriteTweets/', verifyToken, function(req, res) {
const favoritesPromise = () => {
return new Promise((resolve, reject) => {
var favorites = [];
dbConn.then(function(database) {
var dbo = database.db("twitter_search");
dbo.collection('users').findOne({
_id: ObjectId(req.userId)
}, function(err, result) {
if (err) reject(err);
if (!result.hasOwnProperty('favorite_tweets')) {
res.status(404).json({
msg: 'record not found'
});
} else {
result.favorite_tweets.forEach(function(tweet) {
T.get('statuses/show', {
id: tweet.id
}, function(err, data, response) {
if (!err) {
favorites.push(data);
console.log(data); //this returns data
} else {
console.log(err);
reject(err);
}
});
resolve(data);
});
console.log(favorites);
// res.status(200).json({msg:'success', data:favorites});
}
});
}).catch(function(e) {
reject(e)
})
});
}
// Here you call the promise to retrieve "favorites"
favoritesPromise().then(favoritesList => {
console.log('your favorites array', favoritesList)
})
})
Try next code
app.post('/fetchFavoriteTweets/', verifyToken, function (req, res) {
var favorites = [];
dbConn.then(function (database) {
var dbo = database.db("twitter_search");
dbo.collection('users').findOne(
{ _id: ObjectId(req.userId) }, function (err, result) {
if (err) throw err;
if (!result.hasOwnProperty('favorite_tweets')) {
res.status(404).json({ msg: 'record not found' });
}
else {
// Counter
let count = result.favorite_tweets.length;
result.favorite_tweets.forEach(function (tweet) {
T.get('statuses/show', { id: tweet.id }, function (err, data, response) {
// Decrease count
count -= 1;
if (!err) {
favorites.push(data);
// Check if count is zero
if (count === 0) {
console.log(favorites);
res.status(200).json({msg:'success', data:favorites});
}
} else {
console.log(err);
}
});
});
}
});
}).catch(function (e) { console.log(e) })
});
I have read other related posts and still am not understanding correctly how to use promises.
router.get('/', ensureAuthenticated, function(req, res) {
let promiseToGetResponses = new Promise(function(resolve, reject) {
var indexData = new getIndexData();
resolve(indexData);
console.log('received ' + indexData.length);
});
promiseToGetResponses.then(function(data) {
console.log('then data length ' + data.length);
res.render('index', {rsvpsIn: data});
}).catch(function() { });
});
Console shows this:
received undefined
then data length undefined
returned 1 *** this is from a console.log inside the getIndexData().
The function is getting the data, but my promise usage is not waiting for it.
Thanks.
P.S. I didn't know the getIndexData function was needed. Here it is:
function getIndexData(){
RSVP.find({response: 'in'}, function (err, data) {
if (err) throw err;
// This will be a list of all responses to show in the view
var rsvpsIn = [];
if (data.length > 0) {
// Use because the foreach loop below has async calls.
var responseCounter = data.length;
data.forEach(function(response) {
var foundUser = User.getUserById(response.userId, function(err, user) {
var newRSVP = {userName: user.username, notes: response.notes};
rsvpsIn.push(newRSVP);
// decrement and if we are done, return list
responseCounter -= 1;
if (responseCounter == 0) {
console.log('returned ' + rsvpsIn.length);
return rsvpsIn;
}
});
});
} else {
return rsvpsIn;
}
});
}
Not familiar with RSVP but it appears to be callback based API, so you should wrap it in a promise and just use the promise directly:
function getIndexData(){
return new Promise((resolve, reject) => {
RSVP.find({response: 'in'}, function (err, data) {
if (err) reject(err);
// This will be a list of all responses to show in the view
var rsvpsIn = [];
if (data.length > 0) {
// Use because the foreach loop below has async calls.
var responseCounter = data.length;
data.forEach(function(response) {
var foundUser = User.getUserById(response.userId, function(err, user) {
var newRSVP = {userName: user.username, notes: response.notes};
rsvpsIn.push(newRSVP);
// decrement and if we are done, return list
responseCounter -= 1;
if (responseCounter == 0) {
console.log('returned ' + rsvpsIn.length);
resolve(rsvpsIn);
}
});
});
} else {
resolve(rsvpsIn);
}
});
});
}
then just use it where you use the promise:
router.get('/', ensureAuthenticated, function(req, res) {
getIndexData().then(function(data) {
console.log('then data length ' + data.length);
res.render('index', {rsvpsIn: data});
}).catch(function() { });
});
guys. I cant resolve some prolem with js callback return.
So, we have next func:
//Функция получения контакт листа пользователя
function get_contact_list(data) {
//Берем ID пользователя, который к нам пытается обратиться исходя из SESSION_ID
conn.query('SELECT id FROM users WHERE session_id="' + data['auth'] + '" LIMIT 1;', function(err, res) {
if (err) {
console.log(err);
}
//Разбираем результат
res.fetchAll(function(err, row) {
if (err) {
console.log(err);
}
//А теперь, собсна, выбираем контакты
conn.query('SELECT u.id, u.sname, u.fname, u.nick FROM users as u LEFT JOIN contacts AS c ON c.dep_id = u.id WHERE owner_id =' + row[0].id + ';', function(err, res) {
if (err) {
console.log(err);
}
//Разбираем результат
res.fetchAll(function(err, row) {
if (err) {
console.log(err);
}
//TAKE row HEREOF
NEED_OUT = row;
});
});
});
});
return NEED_OUT;
}
I need return variable row from 2-lvl callback, but if i create global var or write "row" in global object, it's not working. Help me pls! Thank you!
I believe you didn't get the concept of async code right. Because you example is way to long, here some pseudo code that shows you in which order async code will work.
// Timestamp: 0
function foo(input, cb) {
// Timestamp: 2
doSomeThingAsync(input, (result) {
// Timestamp: 5
result = transformResultSync(result);
cb(result);
});
// Timestamp: 3
}
// Timestamp: 1
foo('bar', function(result) {
// Timestamp: 6
console.log(result);
});
// Timestamp: 4
I hope it helps a little bit.