nodejs return from callbacks - javascript

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.

Related

array push results in empty array javascript

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) })
});

How to avoid a callback in promise

I am new to NodeJS and JavaScript. I am badly stuck in a problem:
I want to generate QR image of 'some text' and after generating it, I want to query my MySQL database and insert the image to database.
Problem is that QRCode.toDataURL of SOLDAIR module goes in running state and query is called before the QR image is returned from the .toDataUrl function.
Hence it results in error.
I tried everything, promises, nested promises, counters, if statements etc., but I am unable to find a solution.
My code:
router.post('/generateTicket', (req,res) => {
const query1 = `SELECT * FROM booking ORDER BY bookingID DESC LIMIT 1`;
const query2 = `INSERT INTO ticket (ticket_image,BookingID) SET ?`;
let bookingID;
let count;
let ticket_data = {};
Promise.using(mysql.getSqlConn(), conn => {
conn.query(query1).then(resultOfQuery1 => {
bookingID = resultOfQuery1[0].BookingID;
count = resultOfQuery1[0].PeopleCount;
console.log("ID = " + bookingID + " people count = "+count);
promiseToCreateQRcode().then(function (URLfromResolve) {
console.log("the url is " + URLfromResolve );
}).catch(function (fromReject) {
console.log("Rejected "+ fromReject);
}); // catch of promise to create QR
}).catch(err => {
res.json({ status: 500, message: 'Error Occured in query 1 ' + err });
}); // catch of query 1
});
});
var opts = {
errorCorrectionLevel: 'H',
type: 'image/png',
rendererOpts: {
quality: 0.3
}
};
let promiseToCreateQRcode = function () {
let QRImage;
return new Promise(function (resolve,reject) {
QRCode.toDataURL('text', function (err, url) {
if (err) throw err
console.log("\n"+url+"\n");
QRImage = url;
});
if (QRImage)
resolve(QRImage);
else
reject("error occured in url");
});
};
As u can see, the program jumps to if statement and the QR image is not generated yet, hence it goes in "reject":
Try this,
let promiseToCreateQRcode = function () {
return new Promise(function (resolve,reject) {
QRCode.toDataURL('text', function (err, url) {
if (err){
reject(err); // or some message
} else {
resolve(url);
}
});
});
};
This way promise will be resolved only when toDataURL returns QR image.
Have a look at How do I convert an existing callback API to promises?. You need to call resolve or reject in the asynchronous callback!
function promiseToCreateQRcode() {
return new Promise(function(resolve,reject) {
QRCode.toDataURL('text', function (err, url) {
if (err) {
reject(err);
} else {
console.log("\n"+url+"\n");
resolve(url);
}
});
});
}
Using this extra QRImage variable like you did cannot work.

Pass Data Value from MongoDB to ejs Template in Node.js

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.

Callback/Promises implementation for a boolean check

Currently I have the following callback system:
var saveTask = function(err, result) {
if (err) return callback(err, result);
var newid = mongoose.Types.ObjectId();
var task = new Task({
_id: newid,
taskname: req.body.name,
teamid: req.body.team,
content: req.body.content,
creator: req.user.userId
});
task.save(function (err) {
if (!err) {
log.info("New task created with id: %s", task._id);
return callback(null, task);
} else {
if(err.name === 'ValidationError') {
return callback('400', 'Validation error');
} else {
return callback('500', 'Server error');
}
log.error('Internal error(%d): %s', res.statusCode, err.message);
}
});
};
if (req.body.team) {
valTeam.isMember(req.body.team, req.user._id, function (err, done) {
if (err) {
saveTask('403', 'Not the owner or member of this team');
} else {
saveTask(null, true);
}
});
} else {
saveTask(null, true);
}
valTeam.isMember
exports.isMember = function(teamid, userid, callback) {
Team.find({'_id':teamid, $or:[{'creator': userid }, {'userlist': { $in : [userid]}}]}, function(err, result) {
if (err) return err;
console.log(result);
if (!result.length)
return callback('404', false);
else
return callback(null, true);
});
}
In short, if team is sent by POST, I'm checking if the user is member of that ID in valTeam.isMember. Am I using the correct syntax and best method to call back my saveTask function to save the task if the user is part of the team?
This code currently works, but I feel like there should be an easier way to do it? How could I use a promise to achieve the same thing?
Thanks in advance.
It's curious the fact that you create objects instead Schemas. However "every head is a different world", this is my way:
task.save(function(error, data){
if (error) {
trow error;
} else {
//Make whatever you want here with data
});

Mongoose update after save

I'm missing something about callbacks with Mongoose save function.
I am trying to insert a new transaction, then if that is successful, update a user account. The problem, I believe, This cause all people to be updated with the last person's amount. What am I trying to do is update a document after saving another document.
Here is the code. Please let me know what I am doing wrong.
Thanks in advance.
//find all schedules
SchedModel.find({ Day_Of_Week: day }, null, null, function (err, Sched) {
if (!err) {
//iterate through what we find
for (var key in Sched) {
if (Sched.hasOwnProperty(key)) {
var val = Sched[key];
console.log("val : " + val);
var Sched_Descr = day || ' Sched Trans';
var this_trans = new TransModel({
mID: val.K_Id,
mDate: today,
mDescr: Sched_Descr,
mAmt: val.mAmt
});
//insert the new trans
this_trans.save(function (err, trans) {
if (!err) {
//when we insert new trans, get the update model
MyModel.findById(val.K_Id, function (err, Model) {
Model.Balance = Model.Balance + val.mAmt;
//save model, this update to the last in the list
Model.save(function (err) {
if (!err) {
console.log("updated");
} else {
console.log(err);
}
});
});
} else {
return console.log(err);
}
});
}
}
} else {
console.log(err);
};
});
Update: ES6's let solves this pretty trivially, just replace var with let in your original code and it should work.
Your this_trans and such variables aren't unique in each iteration of the for-in loop. You might wanna wrap it in a self-executing anonymous function scope ((function(){})())
//find all schedules
SchedModel.find({ Day_Of_Week: day }, null, null, function (err, Sched) {
if (!err) {
//iterate through what we find
for (var key in Sched) {
(function(key){ // self-executing anonymous function scope
if (Sched.hasOwnProperty(key)) {
var val = Sched[key];
console.log("val : " + val);
var Sched_Descr = day || ' Sched Trans';
var this_trans = new TransModel({
mID: val.K_Id,
mDate: today,
mDescr: Sched_Descr,
mAmt: val.mAmt
});
//insert the new trans
this_trans.save(function (err, trans) {
if (!err) {
//when we insert new trans, get the update model
MyModel.findById(val.K_Id, function (err, Model) {
Model.Balance = Model.Balance + val.mAmt;
//save model, this update to the last in the list
Model.save(function (err) {
if (!err) {
console.log("updated");
} else {
console.log(err);
}
});
});
} else {
return console.log(err);
}
});
}
})(key);
}
} else {
console.log(err);
};
});

Categories