Sails Waterline ORM query inside array.map() - javascript

Code snippet below is using sails waterline ORM to make DB queries and sending response. However, the execution flow is weird, The code outside of map function is running before the map function has finished executing. 'I am outside of map' is being printed in the console before 'I am inside of map'. I think it can be solved this using Promise or async / await. I have tried using Promise.all() below, but it doesn't work, the response is always an empty array. I would be grateful if you could give an example on how to solve this kind of issues.
allMembers: (req, res) => {
const projectId = req.params.id;
ProjectMembers.find({projectId: projectId}).exec( (err, members) => {
if(err) res.serverError("bad request!");
if(members.length === 0) res.notFound({message: "No members are found for this project!"});
let membersInfo = [];
let promise = Promise.all(members.map(m => {
User.findOne({id: m.userId}).exec( (err, user) => {
if(err) membersInfo.push({name: null, userId: null, email:null,rate:null, error: 'Internal error!'})
else if(!user) membersInfo.push({name: null, userId: null, email:null,rate:null, error: 'No user found'})
else membersInfo.push({name: user.name, userId: user.id, rate: m.rate, error: null})
console.log("i am inside of map");
})
}));
console.log("I am outsie of map")
promise.then( (resolve) => {return res.ok({members: membersInfo})});
}

I was about to tell you "don't use queries in .map", but on looking, I think your code is quite close to working. The argument of Promise.all has to be an array of promises. Each User.findOne is indeed a promise - the stumbling block is that once you use .exec it no longer returns a promise.
I think the answer is to do your processing inside the .then instead of right inside the .map:
ProjectMembers.find({projectId: projectId}).exec( (err, members) => {
if(err) return res.serverError("bad request!");
if(members.length === 0) return res.notFound({message: "No members are found for this project!"});
let promise = Promise.all(members.map(m => User.findOne({id: m.userId})));
promise.then( (values) => {
// values is the array of user objects returned from the array of queries
let membersInfo = values.map((user) => {
if (!user) {
return {name: null, userId: null, email:null,rate:null, error: 'No user found'};
} else {
return {name: user.name, userId: user.id, rate: m.rate, error: null};
}
});
return res.ok({members: membersInfo});
}, (err) => {
return res.serverError("Error finding users");
});
The promise only has a single fail callback, so you lose the ability to individually catch and handle querying errors (though you can still individually handle not-found results).

Related

UnhandledPromiseRejection undefined problem

I hope you can help me. I am developing a functionality that reads a series of data (data is taked from csv file) and checks if it has to put it in a list or not. The problem comes when I start to check the data (through promises) since it gives me an error telling me that the rejected promise has not been captured. You will need to use the following:
// -npm install email-existence
const emailExistence = require("email-existence");
The code:
function checkEmailExistencePromise(element) {
return new Promise((resolve, reject) => {
emailExistence.check(element.email, (error, response) => {
if (error) {
reject(error);
return;
}
// If the person has false email the promise will be save (as object) with "Exists" attribute in false.
if (!response) {
resolve({
name: element.name,
phone: element.phone,
email: element.email,
document: element.document,
weight: element.weight,
tags: element.tags,
exists: false,
});
return;
}
// If the person has valid email the promise will be save (as object) with "Exists" attribute in true.
resolve({
name: element.name,
phone: element.phone,
email: element.email,
document: element.document,
weight: element.weight,
tags: element.tags,
exists: true,
});
});
}).catch(() => {
throw console.error();
});
}
// I call the function that will write the CSV file with valid email records.
checkEmails();
// This function collects the promises of the "checkEmailExistencePromise" function and dumps them into an array.
async function checkEmails() {
const promises = sinRepetidos.map((element) =>
checkEmailExistencePromise(element)
);
const values = await Promise.all(promises);
// Here we go through the promises (which are also objects) and those with the true attribute I put them in a new array that will be printed later.
values.forEach((element) => {
if (element.exists === true) {
checked.push(element);
}
});
Because checkEmailExistencePromise() can throw an error (both through the reject() and the throw call), you need to wrap your
const values = await Promise.all(promises);
call in checkEmails() in a try..catch as well, like so
let values = null;
try {
values = await Promise.all(promises)
} catch (e) {
console.error(e)
}
// do something with values, if it's not null
Edit
As you most likely don't want checkEmailExistencePromise to throw an error, you can replace it with this:
function checkEmailExistencePromise(element) {
// NOTE: we're making is so that this promise never rejects - if there's
// an error in there, we'll assume that the email isn't valid
return new Promise(resolve => {
emailExistence.check(element.email, (error, response) => {
let exists = false;
if (error) {
// we can log the error, to make sure we're not doing anything wrong
// that needs to be fixed - some errors can be legit, though
console.error(error);
}
// NOTE: we should probably actually check the response
if(response) {
exists = true;
}
resolve({
name: element.name,
phone: element.phone,
email: element.email,
document: element.document,
weight: element.weight,
tags: element.tags,
exists
})
});
})
}
We take any error to mean that the email isn't valid.
Also, if element only contains those 6 properties (name, phone, email...), then you can simplify the resolve further with something like this:
resolve(Object.assign({},element,{exists}))
This will make a shallow clone of the object and add the exists property to it

Javascript: using catch block but not to handle an error

I'm in a situation where I have to use a catch block to execute some code but I don't want to consider it an error.
Basically, I want to update/create a user based on whether the user is already registered or not respectively. The admin sdk let me create a user, and if the user already exists it throws an error. So if I'm in the catch block I know that the user already exists and I want to update it.
function addClient(client) {
return new Promise((resolve, reject) => {
admin.auth().createUser({
uid: client.id,
email: client.email,
emailVerified: true,
password: client.password,
}).then(record => {
resolve(record);
return null;
}).catch(
// the user already exist, I update it
admin.auth().updateUser(client.id, {
email: client.email
}).then(record => {
resolve(record);
return null;
}).catch(
err => {
reject(err);
}
)
);
});
}
The problem is that when I call the function with an existing user, it is updated correctly but the HTTP response is an internal server error (I guess because it enters the catch block and it considers this as an error). The same is if I send a new user: it is created correctly but the HTTP response code is a 500.
There is a way to avoid this behaviour?
This is the main function that calls the previous one for each user received and it's responsible for sending the HTTP response:
exports.addClients = functions.https.onRequest((req, res) => {
// fetch recevied list from payload
var receivedClients = req.body.clients;
var promises = [];
receivedClients.forEach(client => {
promises.push(addClient(client));
})
Promise.all(promises)
.then(() => {
res.sendStatus(200);
return null;
})
.catch(err => {
res.status(500).send(err);
});
});
I guess that what I want to achieve is to have all the promises resolving.
You need to pass a callback to .catch, not a promise. Also avoid the Promise constructor antipattern!
function addClient(client) {
return admin.auth().createUser({
uid: client.id,
email: client.email,
emailVerified: true,
password: client.password,
}).catch(err => {
// ^^^^^^^^
// if (err.code != "UserExists") throw err;
return admin.auth().updateUser(client.id, {
email: client.email
})
});
}

Page does not load when using the .then function

I have been working on a project and it requires me to use the .then function after creating schemas in the database but for some reason, every time I try to run the res.render function in the .then()function, the page does not load. It tells me my app is running on the port, and I am connected to my database, and everything seems to be fine. I am using cloud9, and here is my code for the index.js file
function getVenues (bars){
bars.map((eachBar) => {
Venue.findOne({
id: eachBar.id,
title: eachBar.name,
image: eachBar.image_url,
url: eachBar.url,
rating: eachBar.rating
}, (err, venue) => {
if(err) return (err);
if(!venue){
var newVenue = new Venue({
id: eachBar.id,
title: eachBar.name,
image: eachBar.image_url,
rating: eachBar.rating,
url: eachBar.url
}).save((err, venue) => {
if(err) return err;
})
}
})
})
}
router.get('/', function (req, res) {
res.header('Access-Control-Allow-Credentials', true);
request.get('http://ipinfo.io/' + req.headers['x-forwarded-for'], {json: true}, function (e, r){
client.search({
term: "bars",
latitude:r.body.loc.split(",")[0],
longitude: r.body.loc.split(",")[1]
}).then(response => {
getVenues(response.jsonBody.businesses).then(function(results){
res.render('home', {
bars: results,
term: 'Bars near you',
authenticated: req.isAuthenticated()
});
console.log(results);
});
console.log(req.isAuthenticated());
});
});
});
Everything is defined and it shows no errors in my code. The problem here is that if I put the res.render function outside of the .then function, it works perfectly fine and everything loads, but right now, it just keeps on loading and gives be an error of Error 502 - Bad Gateway in the cloud9 site. Does anyone know why it happens and how to solve it?
The problem is, that your function getVenues doesn't return a Promise which would define then and catch. To get an array of venues as the result, you need to combine the results of each Venue.findOne call. This can be done by using Promise.all on the array of 'inner' Promises:
return Promise.all(bars.map(bar => {
return Venue.findOne({...})
}))
This assumes that Venue.findOne returns value (or again a Promise). This is easy, if findOne already returns a Promise (should work like above) but works as follows if it only uses callbacks:
Venue.findOne({...}, (err, venue) => {
if (err) return (err)
if (venue) return venue
return new Promise((fulfil, reject) => {
let newVenue = new Venue({...})
.save((err, venue) => {
if (err) reject(err)
else fulfil(venue)
})
})
Promises might return errors. For each promise you use, you should add a catch. Just add a catch right after the closing bracket of each then (or the last then if - in general - you have a cascade of thens) and you should see which error occurs:
client.search(...)
.then(...)
.catch(error => {
console.log(error)
})

My UserModel is not returning the proper user data finding _id?

This is my code:
var user = UserModel.findOne({ _id: decodedToken.id, }, function (err, user) {
if (err) return handleError(err)
console.log('inside UserModel user = ', user) // this is what I want to return on the variable user
});
console.log("Outside userModel = ",user) // the user inside the findOne callback isn't assigned on the variable user?
Inside the UserModel.findOne callback it returns the proper user:
{ _id: 5979744a02bcec1dc873a96c,
updatedAt: 2017-07-27T05:04:10.313Z,
createdAt: 2017-07-27T05:04:10.313Z,
fullName: 'kring kring',
email: 'sofbaculio#gmail.com',
password: '$2a$08$yiDO.HBGoieBApY8VCHA/Opp6PEpq7.CwZmfc2CkQmQoRfB/ySi4u',
__v: 0,
active: true }
but outside the callback it returns something like this:
Query {
_mongooseOptions: {},
mongooseCollection:
NativeCollection {
collection: Collection { s: [Object] },
opts: { bufferCommands: true, capped: false },
name: 'user',
Help?
Thats because your function actually returns a Mongoose query which you can later use to invoke the method exec and use it to get the results.
You need to now execute that Query to get the results:
user.exec(function(err,result){
if(err)
return console.log(err);
console.log(result);
});
You can read more about Querying with Mongoose # http://mongoosejs.com/docs/queries.html
Well the issue is its a async call so in that case you have to implement a promise to achieve this.
Also if you are going to find in model using mongo Object Id which is _id then you should go for UserModel.findById(decodedToken.id) instead of UserModel.findOne({ _id: decodedToken.id, }. Just try it and next time when you want to query a collection by Object Id just go for Model.findById its a good practice. hope it helps.
ok follow these steps one by one I am trying my best to help you.
First you need to install q library to implement promise. you can install it by using
npm install q.
getUser(decodedToken.id).then(function(res){
console.log(res);
var user = res;
//Just do it here what ever you want to do
},function(err){
console.log(err)
})
//Here is your promise that you going to return
function getUser(id) {
return Q.Promise(function(resolve, reject) {
UserModel.findById(decodedToken.id), function (err, user) {
if (err) return reject(err)
console.log('inside UserModel user = ', user)
resolve(user)
});
})
}

Bluebird promise resolve(data) is undefined in client code

Hiyas. I have a simple app whereby a client is expecting a promise as a result, but upon calling the resolve() method, the promise keeps returning undefined as the result.
The client code:
UsersRepo.findOneAsync({id: id}).then(function(err, result) {
console.log("UserService promise resolution", err, result);
});
This outputs "null" and "undefined", for err and result, respectively
The code that is doing the work and returning the promise:
findOneAsync: function(args) {
var where = ""; //omitted
var promise = new Promise(function(resolve, reject) {
db.query("Select * from users" + where + " limit 1", function(err, result) {
var res = { id: 1, username: 'username', firstName: 'First', lastName: 'Last' };
if(err != null) {
console.log("REJECT", err);
reject(err);
}
else {
console.log("RESOLVE", res);
resolve(null, res);
}
});
});
return promise;
}
As you can see, I'm just returning some static data for this test (the 'res' variable). In the client code, the console statement always prints out:
UserService promise resolution null undefined
I don't get this. It looks like I'm doing everything correctly: calling the resolve() method with the data, and the client code is using .then(function(err, result)) properly, so it seems. Why isn't data being received from the client end?
Thanks for any help!
==>
Solution:
As mentioned below, Bluebird's reject and resolve only take one argument. The first 'null' was only being seen. Changing the code to 'resolve(res)' worked. Thanks guys.
Resolve/Reject both accept a single parameter ... hence why your result is undefined, as the second value you pass to resolve is never used, and the first is null
What you want to do instead is
UsersRepo.findOneAsync({id: id}).then(function(result) {
console.log("UserService promise resolution", result);
}).catch(function(err) {
console.log("UserService promise error", err);
});
findOneAsync: function(args) {
var where = ""; //omitted
var promise = new Promise(function(resolve, reject) {
db.query("Select * from users" + where + " limit 1", function(err, result) {
var res = { id: 1, username: 'username', firstName: 'First', lastName: 'Last' };
if(err != null) {
console.log("REJECT", err);
reject(err);
}
else {
console.log("RESOLVE", res);
resolve(res);
}
});
});
return promise;
}
Bluebird's then does not take in an err. Instead, then takes in two functions, the first being for resolve, and the second for being reject. you can also use catch for handling errors.
http://bluebirdjs.com/docs/api/then.html
Edit: #JaromandaX is correct, the resolve function should only take in one parameter.
You should only resolve(res), in addition to not expecting the error object to get passed into the then callback.

Categories