Async/await function yields on undefined - node.js

I'm having trouble with an async function. I'm making a query (using mongoose) to a mongodb and when I try to get the info back it yields undefined.
Here's my query to the db (nested within a function):
function kquery() {
Krakentick.findOne(
{
iname: 'btcusd',
n: { $lt: now }
},
'mk c n',
function(err, tick) {
if (err) {
return console.log(err);
} else {
return tick;
}
}
);
}
and here's my async/await function:
async function compare() {
var ktick = await kquery();
console.log(ktick);
}
compare();
These functions are both in the same file, and when I run it it gives me an 'undefined'.
While, when I just run the query function and puts up a console.log(tick) instead of the return tick, I get the correct information:
{ _id: 59d1199cdbbcd32a151dcf21,
mk: 'kraken',
c: 430900,
n: 1506875804217 }
I think, I'm messing up with the callback somewhere but I'm not sure where or how.
Here's the full file:
const mongo = require('mongodb');
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const server = mongoose.connect('mongodb://localhost/cryptoCollection', {
useMongoClient: true
});
//Loading the mongoose schema:
const { Krakentick } = require('./kraken/model/krakenModel');
var now = Math.floor(new Date());
function kquery() {
Krakentick.findOne(
{
iname: 'btcusd',
n: { $lt: now }
},
'mk c n',
function(err, tick) {
if (err) {
return console.log(err);
} else {
return tick;
}
}
);
}
async function compare() {
var ktick = await kquery();
console.log(ktick);
}
compare();
Thanks in advance for your help!

Your kquery function must return the promise :
function kquery() {
return Krakentick.findOne(
{
iname: 'btcusd',
n: { $lt: now }
},
'mk c n',
function(err, tick) {
if (err) {
return console.log(err);
} else {
return tick;
}
}
);
}

Just posting the promesified kquery function for reference:
function kquery() {
return new Promise((resolve, reject) => {
Krakentick.findOne(
{
iname: 'btcusd',
n: { $lt: now }
},
'mk c n',
function(err, tick) {
if (err) {
reject(err);
} else {
resolve(tick);
}
}
);
});
}
The accepted answer was the one above although it had been discussed and documented previously in the comments by Mörre! I just wanted to leave it there in case it helps someone!
Thanks again for your valuable help!

Related

can't use the results of a mongoose query when called from another function

I'm writing a blog engine using express, and ran into a problem when trying to run a mongoose query through a function:
What I'm trying to do is to obtain a variable that contains the next and previous blog posts by id, to do that I wrote this function:
middleware.getAdjacentPosts = async function(_id) {
var adjacentPosts = {}
await Post.findOne({ _id: { $gt: _id } }).sort({ _id: 1 }).exec(async function(err, nextPost) {
if (err) {
console.log(err)
} else {
if (nextPost == null) {
adjacentPosts.nextPost = false;
} else {
adjacentPosts.nextPostUrl = nextPost.slug;
adjacentPosts.nextPostTitle = nextPost.title;
}
await Post.findOne({ _id: { $lt: _id } }).sort({ _id: -1 }).exec(
async function(err, previousPost) {
if (err) {
console.log(err.message);
} else {
if (previousPost == null) {
adjacentPosts.previousPost = false;
} else {
adjacentPosts.previousPostUrl = previousPost.slug;
adjacentPosts.previousPostTitle = previousPost.title;
}
console.log(adjacentPosts)
return adjacentPosts
}
})
}
})
}
Before returning, I can see the variable completed with what I need through the console.log. The problem I have is that when I try to execute the function, the receiving variable is empty. This would be executed in the get route for a post, like the following:
Router.get("/posts/:slug", async function(req, res) {
await Post.findOne({ slug: req.params.slug }).populate('categories').populate('comments').exec(async function(err, foundBlog) {
if (err) {
console.log(err.message)
} else {
var posts = {}
posts = await middleware.getAdjacentPosts(foundBlog._id)
console.log(posts)
res.render("frontoffice/post", {
blog: foundBlog,
postUrl: req.params.slug,
adj: posts,
reCaptchaSiteKey: process.env.CAPTCHA_SITE_KEY
})
}
})
})
Any clues of what I might be doing wrong?
As #LucaKiebel suggests, you will need to return the results from your findOnes:
middleware.getAdjacentPosts = async function(_id) {
var adjacentPosts = {};
return await Post.findOne({ _id: { $gt: _id } })
.sort({ _id: 1 })
.exec(async function(err, nextPost) {
if (err) {
console.log(err);
} else {
if (nextPost == null) {
adjacentPosts.nextPost = false;
} else {
adjacentPosts.nextPostUrl = nextPost.slug;
adjacentPosts.nextPostTitle = nextPost.title;
}
return await Post.findOne({ _id: { $lt: _id } })
.sort({ _id: -1 })
.exec(async function(err, previousPost) {
if (err) {
console.log(err.message);
} else {
if (previousPost == null) {
adjacentPosts.previousPost = false;
} else {
adjacentPosts.previousPostUrl = previousPost.slug;
adjacentPosts.previousPostTitle = previousPost.title;
}
console.log(adjacentPosts);
return adjacentPosts;
}
});
}
});
};
A potential improvement, since you are using async/await anyway, might be to get rid of the callbacks:
middleware.getAdjacentPosts = async function(_id) {
var adjacentPosts = {};
try {
const nextPost = await Post.findOne({ _id: { $gt: _id } }).sort({ _id: 1 });
if (nextPost == null) {
adjacentPosts.nextPost = false;
} else {
adjacentPosts.nextPostUrl = nextPost.slug;
adjacentPosts.nextPostTitle = nextPost.title;
}
const previousPost = await Post.findOne({ _id: { $lt: _id } }).sort({ _id: -1 })
if (previousPost == null) {
adjacentPosts.previousPost = false;
} else {
adjacentPosts.previousPostUrl = previousPost.slug;
adjacentPosts.previousPostTitle = previousPost.title;
}
console.log(adjacentPosts);
return adjacentPosts;
} catch (err) {
console.log(err);
}
};
``

Need help for mongoose .find()

I need the result of Mongoose's find().exec in the below format. Is it possible to format the result like that?
var myFunction = function(foo, bar) {
model1.find({ elem: foo, elem2: bar }).exec(function(err, data) {
if (err) {
/* ... */
}
if (data) {
if (data.passed == true) {
return { passed: true, point: data.point };
} else {
return { passed: false, point: data.point };
}
} else {
return { passed: false, point: "not tried" };
}
});
};
object = {
...
someitem: array.map(function(arr) {
return {
_id: program._id,
title: program.title,
slug: program.slug,
status: myFunction(arr._id, arr._id2) /* Like This */
};
});
...
}
and excuse me for My bad english :D
You have to return promise from the function in order to get the values which are coming form mongoose query.So change your function to be:
var myFunction = function(foo,bar) {
return new Promise(function(resolve, reject){
model1.find({elem : foo, elem2 : bar}).exec(function (err, data){
if(err){
/* Blablabla*/
reject(err);
}
if(data){
if(data.passed == true){
resolve({passed:true, point:data.point});
} else {
resolve({passed:false, point:data.point});
}
} else {
resolve({passed:false, point:'not tried});
}
});
});
}
Than if you want to call the function and get the value you have to do like:
let promises, obj_elem = [];
for(let i =0;i < array.length;i++){
promises.push(myFunction(array[i]._id, array[i]. _id2));
}
/* You can't do it sync manner so you have to use some kind of async process */
Promise.all(promises).then((result)=>{
for(let i =0;i < result.length;i++){
obj_elem.push({
_id: program._id,
title: program.title,
slug: program.slug,
status: result[i].passed
});
}
console.log(obj_elem); //here you will have values you want
});

Unable to retrive data and push inside loop in node js

I am trying to retrieve attendance list along with user details.
I am using caminte.js(http://www.camintejs.com/) Cross-db ORM for database interaction.
Here is my code sample of model function "attendanceList".
exports.attendanceList = function (req, callback) {
var query = req.query;
var searchfilters = {};
if(!req.user){
callback({ code:400, status:'error', message: 'Invalid Request', data:{}});
}else{
searchfilters["vendor_id"] = parseInt(req.user._id);
}
if(query.location && parseString(query.location) != '') {
searchfilters["location"] = parseString(query.location);
}
if (query.device_details && parseString(query.device_details) != '') {
searchfilters["device_details"] = parseString(query.device_details);
}
if(query.created_on) {
searchfilters["created_on"] = query.created_on;
}
if(query.status) {
searchfilters["status"] = { regex: new RegExp(query.status.toLowerCase(), "i") };
}
var SkipRecord = 0;
var PageSize = 10;
var LimitRecord = PageSize;
var PageIndex = 1;
if(query.pagesize) {
PageSize = parseInt(query.pagesize);
}
if(query.pageindex) {
PageIndex = parseInt(query.pageindex);
}
if (PageIndex > 1) {
SkipRecord = (PageIndex - 1) * PageSize;
}
LimitRecord = PageSize;
var SortRecord = "created_on";
if(query.sortby && query.sorttype) {
var sortingBy = query.sortby;
var sortingType = 'ASC';
if(typeof query.sorttype !== 'undefined') {
sortingType = query.sorttype;
}
SortRecord = sortingBy + ' ' + sortingType;
}
Attendance.find({ where: searchfilters, order: SortRecord, limit: LimitRecord, skip: SkipRecord }, async function (err, result) {
if(err){
callback({ code:400, status:'error', message:'Unable to connect server', errors:err });
} else {
await result.map(function(row, i){
User.findById(parseInt(row.user_id), function(err, data){
if(err){
console.log(err);
} else {
result[i]['userDetails'] = data;
}
});
});
await Attendance.count({ where: searchfilters }, function (err, count) {
callback({ code:200, status:'success', message:'OK', total:count, data:result });
});
}
});
};
I am getting only attendance list without user details. How do I force to push user details into attendance list? Any Help!!
Thank You
This behavior is asynchronous. When you're making request to DB, your code keeps running, while task to get data comes to task queue.
To keep things simple, you need to use promises while handling asynchronous jobs.
Rewrite your code from this:
Attendance.find({ where: searchfilters, order: SortRecord, limit: LimitRecord, skip: SkipRecord }, async function (err, result) {
if(err){
callback({ code:400, status:'error', message:'Unable to connect server', errors:err });
} else {
await result.map(function(row, i){
User.findById(parseInt(row.user_id), function(err, data){
if(err){
console.log(err);
} else {
result[i]['userDetails'] = data;
}
});
});
await Attendance.count({ where: searchfilters }, function (err, count) {
callback({ code:200, status:'success', message:'OK', total:count, data:result });
});
}
});
To this:
const findAttendanceFirst = (searchFilters, SortRecord, LimitRecord, SkipRecord) => {
return new Promise((resolve, reject) => {
Attendance.find({ where: searchFilters, order: SortRecord, limit: LimitRecord, skip: SkipRecord }, (err, result) => {
if(err) return reject(err);
resolve(result);
});
});
}
const findUserByIdForUserDetails = (userId) => {
return new Promise((resolve, reject) => {
User.findById(parseInt(userId), function(err, data){
if(err) return reject(err);
resolve(data);
})
});
}
const getAttendanceCount = (searchFilters) => {
return new Promise((resolve, reject) => {
Attendance.count({ where: searchFilters }, (err, count) => {
if(err) return reject(err);
resolve(count);
});
})
}
So, now we can use this separate functions to make async behavior looks like sync.
try {
const data = await findAttendanceFirst(searchFilters, SortRecord, LimitRecord, SkipRecord);
for(let userData of data){
try {
userData.userDetails = await findUserByIdForUserDetails(userData.user_id);
} catch(e) {
// Some error happened, so no user details.
// you can set here null or nothing to userDetails.
}
}
let count;
try {
count = await getAttendanceCount(searchFilters);
} catch(e){
// Same as before.
}
const callBackData = { code:200, status:'success', message:'OK', total:count, data:result };
// And here you can do whatever you want with callback data. Send to client etc.
} catch(e) {
}
NB: I've not tested this code, it will be easier for yu to play with your actual data and use Promises and async/await
Just remember that each request to db is asynchronous, and you need to make your code wait for this data.

returning Mongoose query result from Async call

I'm working on a problem where I need to query the db for an instance of a Voter, and use that instance to update an Election, returning to the original function whether that update was successful or not. My code currently looks like this:
function addCandidatesToElection(req, res) {
let electionName = req.body.electionName;
let candidates = req.body.candidates;
let addedCandidatesSucessfully = true;
for(let i=0; i<candidates.length; i++) {
addedCandidatesSucessfully = _addCandidateToElection(electionName, candidates[i]);
console.log("added candidates sucessfully:" + addedCandidatesSucessfully);
}
if(addedCandidatesSucessfully) {
res.send("createElection success");
} else {
res.send("createElection fail");
}
}
which calls this function:
function _addCandidateToElection(electionName, candidateName) {
async.parallel(
{
voter: function(callback) {
Voter.findOne({ 'name' : candidateName }, function(err,voter) {
callback(err, voter);
});
}
},
function(e, r) {
if(r.voter === null){
return 'Voter not found';
} else {
Election.findOneAndUpdate(
{'name': electionName },
{$push: { candidates: r.voter }},
{new: true},
function(err, election) {
if(err){ return err; }
return (election) ? true : false;
});
}
}
);
}
I've already tried printing out the Voter instance(r.voter) to check if it exists (it does), and also printing out the election object returned by the mongoose call, which also works. However, I'm getting a null value in the
addedCandidatesSucessfully = _addCandidateToElection(electionName, candidates[i]);
line, regardless of the result of the call. I think it has to do with the mongoose call returning a local value which is never returned to the function that called _addCandidateToElection, but I don't know how I should return that. I've tried putting control flags such as
let foundAndUpdatedElection = false;
on the first line of _addCandidateToElection and updating it inside the Mongoose query's callback, but apparently it doesn't change.
How should I return the result of the query to the addCandidatesToElection function?
You should probably 'promisify' your code to help you better deal with the asynchronous nature of js. Try the following instead of your example:
function findVoter(candidateName) {
return new Promise(function(resolve, reject) {
Voter.findOne({ 'name' : candidateName }, function(err,voter) {
if(error) {
reject(error);
} else {
resolve(voter);
}
});
});
}
function addCandidateToElection(electionName, candidateName) {
return findVoter(candidateName).then(function(voter) {
return new Promise(function(resolve, reject) {
Election.findOneAndUpdate(
{'name': electionName },
{$push: { candidates: voter }},
{new: true},
function(err, election) {
if (err) {
reject(err);
} else {
resolve(!!election);
}
});
});
}
function addCandidatesToElection(req, res) {
let electionName = req.body.electionName;
let candidates = req.body.candidates;
let addedCandidatesSucessfully = true;
let candidatePromiseArray = [];
for(let i=0; i<candidates.length; i++) {
candidatePromiseArray.push(addCandidateToElection(electionName, candidates[i]));
}
Promise.all(candidatePromiseArray)
.then(function(results) {
console.log(results);
res.send('create election success');
})
.catch(function(error) {
console.error(error);
res.send('failed');
});
}
You will also no longer need to use the async library because promises are now native in ES6

node js mongo db dependencies (doc not being found)

I have the following code:
var method = PushLoop.prototype;
var agent = require('./_header')
var request = require('request');
var User = require('../models/user_model.js');
var Message = require('../models/message_model.js');
var async = require('async')
function PushLoop() {};
method.startPushLoop = function() {
getUserList()
function getUserList() {
User.find({}, function(err, users) {
if (err) throw err;
if (users.length > 0) {
getUserMessages(users)
} else {
setTimeout(getUserList, 3000)
}
});
}
function getUserMessages(users) {
// console.log("getUserMessages")
async.eachSeries(users, function (user, callback) {
var params = {
email: user.email,
pwd: user.password,
token: user.device_token
}
messageRequest(params)
callback();
}, function (err) {
if (err) {
console.log(err)
setTimeout(getUserList, 3000)
}
});
}
function messageRequest(params) {
var url = "https://voip.ms/api/v1/rest.php?api_username="+ params.email +"&api_password="+ params.pwd +"&method=getSMS&type=1&limit=5"
request(url, function(err, response, body){
if (!err) {
var responseObject = JSON.parse(body);
var messages = responseObject.sms
if (responseObject["status"] == "success") {
async.eachSeries(messages, function(message, callback){
console.log(params.token)
saveMessage(message, params.token)
callback();
}, function(err) {
if (err) {
console.log(err)
}
// setTimeout(getUserList, 3000)
})
} else {
// setTimeout(getUserList, 3000)
}
} else {
console.log(err)
// setTimeout(getUserList, 3000)
}
});
setTimeout(getUserList, 3000)
}
function saveMessage(message, token) {
// { $and: [ { price: { $ne: 1.99 } }, { price: { $exists: true } }
// Message.find({ $and: [{ message_id: message.id}, {device_token: token}]}, function (err, doc){
Message.findOne({message_id: message.id}, function (err, doc){
if (!doc) {
console.log('emtpy today')
var m = new Message({
message_id: message.id,
did: message.did,
contact: message.contact,
message: message.message,
date: message.date,
created_at: new Date().toLocaleString(),
updated_at: new Date().toLocaleString(),
device_token: token
});
m.save(function(e) {
if (e) {
console.log(e)
} else {
agent.createMessage()
.device(token)
.alert(message.message)
.set('contact', message.contact)
.set('did', message.did)
.set('id', message.id)
.set('date', message.date)
.set('message', message.message)
.send();
}
});
}
}) //.limit(1);
}
};
module.exports = PushLoop;
Which actually works perfectly fine in my development environment - However in production (i'm using Openshift) the mongo documents get saved in an endless loop so it looks like the (if (!doc)) condition always return true therefore the document gets created each time. Not sure if this could be a mongoose issue - I also tried the "find" method instead of "findOne". My dev env has node 0.12.7 and Openshift has 0.10.x - this could be the issue, and i'm still investigating - but if anybody can spot an error I cannot see in my logic/code please let me know
thanks!
I solved this issue by using a "series" like pattern and using the shift method on the users array. The mongoose upsert findOneOrCreate is good however if there is a found document, the document is returned, if one isn't found and therefore created, it's also returned. Therefore I could not distinguish between the newly insert doc vs. a found doc, so used the same findOne function which returns null if no doc is found I just create it and send the push notification. Still abit ugly, and I know I could have used promises or the async lib, might refactor in the future. This works for now
function PushLoop() {};
var results = [];
method.go = function() {
var userArr = [];
startLoop()
function startLoop() {
User.find({},function(err, users) {
if (err) throw err;
users.forEach(function(u) {
userArr.push(u)
})
function async(arg, callback) {
var url = "https://voip.ms/api/v1/rest.php?api_username="+ arg.email +"&api_password="+ arg.password +"&method=getSMS&type=1&limit=5"
request.get(url, {timeout: 30000}, function(err, response, body){
if (!err) {
var responseObject = JSON.parse(body);
var messages = responseObject.sms
var status = responseObject.status
if (status === "success") {
messages.forEach(function(m) {
var message = new Message({
message_id: m.id,
did: m.did,
contact: m.contact,
message: m.message,
date: m.date,
created_at: new Date().toLocaleString(),
updated_at: new Date().toLocaleString(),
device_token: arg.device_token
});
var query = { $and : [{message_id: m.id}, {device_token: arg.device_token}] }
var query1 = { message_id: m.id }
Message.findOne(query).lean().exec(function (err, doc){
if (!doc || doc == null) {
message.save(function(e) {
console.log("message saved")
if (e) {
console.log("there is an error")
console.log(e)
} else {
console.log(message.device_token)
var messageStringCleaned = message.message.toString().replace(/\\/g,"");
var payload = {
"contact" : message.contact,
"did" : message.did,
"id" : message.message_id,
"date" : message.date,
"message" : messageStringCleaned
}
var note = new apns.Notification();
var myDevice = new apns.Device(message.device_token);
note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
note.badge = 3;
note.alert = messageStringCleaned;
note.payload = payload;
apnsConnection.pushNotification(note, myDevice);
}
})
}
});
});
}
else {
console.log(err)
}
}
});
setTimeout(function() {
callback(arg + "testing 12");
}, 1000);
}
// Final task (same in all the examples)
function series(item) {
if(item) {
async( item, function(result) {
results.push(result);
return series(userArr.shift());
});
} else {
return final();
}
}
function final() {
console.log('Done');
startLoop();
}
series(userArr.shift())
});
}
}
module.exports = PushLoop;

Resources