Execute code synchronously in node.js controller - javascript

Just started with Node.js few weeks ago, i'm trying to execute this code synchronously located in my controller
Quest.js
async function create(req, res, next) {
const formValue = req.body['form'];
let quest;
const riddles = [];
try {
//get current user who created this Quest
await User.findOne({_id: req.body.id})
.then(currentUser => {
/*-------------------------------START QUEST CREATION-------------------------------*/
quest = new Quest({
admin: currentUser,
hunter: new User,
launchDate: formValue.launchDate,
penaltyTime: formValue.penaltyTime,
});
/*-------------------------------END QUEST CREATION-------------------------------*/
/*-------------------------------START RIDDLES CREATION-------------------------------*/
let riddle;
console.log('step1');
//Riddles instantiation
for (const [key, participantEmail] of Object.entries(formValue.participantsEmail)) {
if (formValue.riddle.text == "") {
throw ('errorMsg:IncorrectRiddleText/code:422/field:riddleText');
}
if (formValue.riddle.answer == "") {
throw ('errorMsg:IncorrectRiddleAnswer/code:422/field:riddleAnswer');
}
//if its the first riddle => i.e : the one created by the current user
if (`${key}` == 0) {
riddle = new Riddle({
quest: quest,
author: currentUser,
authorEmail: currentUser.email,
text: formValue.riddle.text,
answer: formValue.riddle.answer,
status: true,
nextRiddle: null
});
}
//if it's a waiting riddle
else {
if (!validator.validateEmail(participantEmail.participantEmail)) {
throw ('errorMsg:IncorrectEmail/code:422/field:participantEmail');
}
if (participantEmail.participantEmail)
riddle = new Riddle({
quest: quest,
authorEmail: `${participantEmail.participantEmail}`,
nextRiddle: null
});
//assign last created riddle to the one before to make the good order
riddles[`${key}` - 1].nextRiddle = riddle;
}
//create riddle list for Quest
riddles.push(riddle);
}
/*-------------------------------END RIDDLES CREATION-------------------------------*/
/*-------------------------------START USER MANAGEMENT-------------------------------*/
//create a User for the hunter or if he already have an account => link it to the new Quest
User.findOne({email: formValue.hunterEmail})
.then(hunterUser => {
console.log('step2');
if (hunterUser == null) {//if userHunter doesn't exist yet => create it
userHelper.createUser(
formValue.hunterFirstName,
formValue.hunterLastName,
formValue.hunterFirstName + formValue.hunterLastName.slice(-1),
formValue.hunterEmail,
Text.generateRandomPassword()
).then((createdUser) => {
console.log('step3');
hunterUser = createdUser;
}).catch(error => {
console.log('error1')
error = Text.parseErrorToObject(error)
return res.status(parseInt(error.code.toString())).json(error);
}
);
}
console.log('step4');
questHelper.saveQuest(quest, riddles, hunterUser)
.then(() => {
console.log('step5');
return res.status(200).json({'msg': 'Quest created'})
})
.catch(error => {
console.log('error2')
error = Text.parseErrorToObject(error)
return res.status(parseInt(error.code.toString())).json(error);
}
);
}
).then().catch(error => {
throw (Text.parseErrorToObject(error));
})
/*-------------------------------END USER MANAGEMENT-------------------------------*/
})
.catch(error => {
throw (Text.parseErrorToObject(error));
}
);
} catch (e) {
console.log('error3')
return res.status(parseInt(e.code.toString())).json(e);
}
};
But when i execute this if i display the logs ihave this :
step1
step2
step4
step3
I know why it's doing this, du to the fact that JS is asynchronous multi-thtreading.
But i can't figure out how to execute this block (step4) :
questHelper.saveQuest(quest, riddles, hunterUser)
.then(() => {
console.log('step5');
return res.status(200).json({'msg': 'Quest created'})
})
.catch(error => {
console.log('error2')
error = Text.parseErrorToObject(error)
return res.status(parseInt(error.code.toString())).json(error);
}
);
When the previous block is over. That mean, when hunterUser = createdUser; is executed
EDIT :
Thanks to all answers, i have cleaned my code and went out of all the then/catch block.
It lost a lot of weight :p and so much readable.
The error management is so much easier this way too
async function create(req, res, next) {
const formValue = req.body['form'];
const riddles = [];
try {
//get current user who created this Quest
const currentUser = await User.findOne({_id: req.body.id});
/*-------------------------------START QUEST CREATION-------------------------------*/
let quest = await new Quest({
admin: currentUser,
hunter: new User,
launchDate: formValue.launchDate,
penaltyTime: formValue.penaltyTime,
});
/*-------------------------------END QUEST CREATION-------------------------------*/
/*-------------------------------START RIDDLES CREATION-------------------------------*/
let riddle;
//Riddles instantiation
for (const [key, participantEmail] of Object.entries(formValue.participantsEmail)) {
if (formValue.riddle.text == "") {
throw ('errorMsg:IncorrectRiddleText/code:422/field:riddleText');
}
if (formValue.riddle.answer == "") {
throw ('errorMsg:IncorrectRiddleAnswer/code:422/field:riddleAnswer');
}
//if its the first riddle => i.e : the one created by the current user
if (`${key}` == 0) {
riddle = new Riddle({
quest: quest,
author: currentUser,
authorEmail: currentUser.email,
text: formValue.riddle.text,
answer: formValue.riddle.answer,
status: true,
nextRiddle: null
});
}
//if it's a waiting riddle
else {
if (!validator.validateEmail(participantEmail.participantEmail)) {
throw ('errorMsg:IncorrectEmail/code:422/field:participantEmail');
}
if (participantEmail.participantEmail)
riddle = new Riddle({
quest: quest,
authorEmail: `${participantEmail.participantEmail}`,
nextRiddle: null
});
//assign last created riddle to the one before to make the good order
riddles[`${key}` - 1].nextRiddle = riddle;
}
//create riddle list for Quest
riddles.push(riddle);
}
/*-------------------------------END RIDDLES CREATION-------------------------------*/
/*-------------------------------START USER MANAGEMENT-------------------------------*/
//create a User for the hunter or if he already have an account => link it to the new Quest
let hunterUser = await User.findOne({email: formValue.hunterEmail});
if (hunterUser == null) {//if userHunter doesn't exist yet => create it
hunterUser = await userHelper.createUser(
formValue.hunterFirstName,
formValue.hunterLastName,
formValue.hunterFirstName + formValue.hunterLastName.slice(-1),//TODO add a unique ID
formValue.hunterEmail,
Text.generateRandomPassword()
)
}
await questHelper.saveQuest(quest, riddles, hunterUser)
return res.status(200).json({'msg': 'Quest created'})
/*-------------------------------END USER MANAGEMENT-------------------------------*/
} catch (error) {
error = Text.parseErrorToObject(error);
return res.status(parseInt(error.code.toString())).json(error);
}
};

clearly you already understand how "async" and "await" work. Now it's just about remembering to apply them for every network I/O operation.
And so if you change
await User.findOne({_id: req.body.id})
.then(currentUser => {
to
await User.findOne({_id: req.body.id})
.then(async currentUser => {
then suddenly you'll be able to use await inside that block.
Now you need to change
User.findOne({email: formValue.hunterEmail})
.then(hunterUser => {
to
await User.findOne({email: formValue.hunterEmail})
.then(hunterUser => {
your current solution basically says "find user and once you're done do this and that..." and then you don't actually wait until the user is found, so that basically has to be fixed.
Better yet, you could drop all of the '.then()' and '.catch' blocks since they can be easily replaced with the "async/await" syntax.
So instead of writing
await User.findOne({email: formValue.hunterEmail})
.then(hunterUser => { ...someLogic })
in a more modern way you could write
const hunerUser = await User.findOne({email: formValue.hunterEmail});
//...someLogic

First, you're using await and .then() at the same line of code, which won't produce any value.
I'd suggest using the ES6 async await keywords alone which will make your different parts of code execeute after each promise has been resolved.
Step 4 should look something like this:
try {
const result = await questHelper.saveQuest(quest, riddles, hunterUser);
console.log('step5');
return res.status(200).json({'msg': 'Quest created'})
}
catch (error) {
console.log('error2')
error = Text.parseErrorToObject(error)
return res.status(parseInt(error.code.toString())).json(error);
}

Any logic that should happen after the asynchronous operation should be moved to its callback function. Currently you have this structure:
User.findOne({email: formValue.hunterEmail})
.then(hunterUser => {
//...
userHelper.createUser(/*...*/)
.then((createdUser) => {
//...
// YOU WANT TO DO SOMETHING HERE
});
// THIS IS THE CODE YOU WANT DO EXECUTE ABOVE
});
If you want that later block of code to be executed in response to the completion of the userHelper.createUser operation, move it to the callback where you respond to the result of that operation:
User.findOne({email: formValue.hunterEmail})
.then(hunterUser => {
//...
userHelper.createUser(/*...*/)
.then((createdUser) => {
//...
// THIS IS THE CODE YOU WANT DO EXECUTE
});
});
Overall it seems you're getting confused by a large and complex set of nested callbacks. The async and await syntax for Promises is meant to address that and make the syntax more clear. If these asynchronous operations return Promises then you're definitely encouraged to make use of that syntax instead of all of these .then() callbacks.
But even with the .then() syntax, all you need to do is identify your blocks of code and where they belong. Either something should execute immediately (after the .then()), or it should execute in response to an asynchronous operation (inside the .then().

Related

javascript promises inside then

I've recently discovered the promises, and they made my life much easier.
But there is a specific case that I'm not being capable to handle. It is when I have to call a promise inside then().
Here is my code:
const firebaseAuth = require("firebase/auth");
const auth = firebaseAuth.getAuth();
const { User } = require('../models/User');
app.post('/create_user', (req, res) => {
user_uid = req.body.params.uid;
newUserEmail = req.body.params.email;
newUserPassword = req.body.params.password;
let user;
firebaseAuth.createUserWithEmailAndPassword(auth, newUserEmail, newUserPassword)
.then((userCredential) => new Promise((resolve, reject) => {
if(userCredential == undefined) throw Error("createUserWithEmailAndPassword failed");
user = new User(userCredential.user.uid, req.body.params.userAttributes);
resolve()
}))
.then(firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null))
.then(/*other functions using User object*/)
.then(() => { // finished promise chaining
res.status(200).send();
})
.catch((e) => {
console.log(e)
res.status(403).send();
})
});
The problem is, the .then(firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null)) is being called before the user is initialized in user = new User(userCredential.user.uid, req.body.params.userAttributes);.
Could someone please help me understand why is this happening? And in case I have to call a promise inside a then(), Do I also have to nest a .catch()? Or my single .catch() at the end of my function will be capable to handle possible errors?
Edit: User does some async tasks inside the constructor, because it has to handle the images.
And I can only Initialize it, after firebaseAuth.createUserWithEmailAndPassword(auth, newUserEmail, newUserPassword) because I have to get the generated Id in userCredential
I see two issues here:
The most simple fix for your code is to change this line:
.then(firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null))
to
.then(() => firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null))
notice the the the arrow function wrapping the call to firebaseAuth, before your code was the equivalent of asking firebaseAuth to return a function that would handle that step of the promise chain.
If the only purpose of return new Promise() is to do the validation and get the user, you can simply.
.then((userCredential) => {
if(userCredential == undefined) throw Error("createUserWithEmailAndPassword failed");
return new User(userCredential.user.uid, req.body.params.userAttributes);
})
and the user will be available in the next chain as
.then(user => {
// do stuff with user
})
You can use async function and try block to await the userCredential value like this:
app.post('/create_user', async(req, res) => {
user_uid = req.body.params.uid;
newUserEmail = req.body.params.email;
newUserPassword = req.body.params.password;
let user;
try {
const userCredential =
await firebaseAuth.createUserWithEmailAndPassword(
auth,
newUserEmail,
newUserPassword
);
if (userCredential == undefined)
return res
.status(400)
.send('❌ CreateUserWithEmailAndPassword failed 🙁');
user = new User(
userCredential.user.uid,
req.body.params.userAttributes
);
await firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null);
return res.status(201).send('Created!! 😀');
} catch (error) {
console.log('❌ Error:', error);
return res.status(400).json({
message: error,
});
}
});
We should never find new Promise() wrapping a library that already creates promises.
Also remember that the form of a chain is....
// promiseA, promiseB & promiseC are expressions that evaluate to promises
return promiseA.then(resolutionOfPromiseA => {
// synchronous work
return promiseB;
}).then(resolutionOfPromiseB => {
// synchronous work
return promiseC;
}).catch(error => {})
In newer syntax:
async function myFunction() {
try {
let resolutionOfPromiseA = await promiseA;
// synchronous work
let resolutionOfPromiseB = await promiseB;
// synchronous work
return promiseC;
} catch(error) {
}
Sticking with the OP's older syntactic style (which is perfectly good as long as it remains consistent)
let user;
firebaseAuth.createUserWithEmailAndPassword(auth, newUserEmail, newUserPassword)
.then(userCredential => {
// note that we don't create a promise
if(userCredential == undefined) throw Error("createUserWithEmailAndPassword failed");
user = new User(userCredential.user.uid, req.body.params.userAttributes);
// note that we return this one. It's good form, even if the next then doesn't use the result
return firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null)
})
.then(res => { // this res is the result of firebaseAuth.sendPasswordResetEmail
// user is in scope here because it's in the containing scope
/* other functions using User object */
})
.then(() => { // finished promise chaining
res.status(200).send();
})
.catch((e) => {
console.log(e)
res.status(403).send();
})
edit If you really wanted user in the block after sendPasswordReset, and you really didn't want to keep a user variable in the containing scope, you could say...
// move let declaration here
let user = new User(//...
// string an extra return user here
// some linters think this is a faux pas
return firebaseAuth.sendPasswordResetEmail(auth, user.attr.email, null)
.then(() => return user)
)}.then(user =>

Discord.js : Variable is undefined despite using await

I'm making a discord bot using discord.js that listens to slash commands in a discord server and manipulates data on a google sheet. I've come across a frustrating problem while trying to make a confirmation system for a command. The issue is with the getConfirmation() method. Here is the relevant code:
async execute(interaction) {
try {
await interaction.deferReply();
// get player from spreadsheet
const iss = interaction.options.getInteger("iss");
const player = await getPlayerByISS(iss);
// get confirmation
let hasConfirmed = await getConfirmation(interaction, player);
if (!hasConfirmed) {
await interaction.editReply("Draft choice abandoned.");
return;
}
// if confirmed, draft the player
let draftedPlayer = await draftPlayer(interaction.user.tag, player);
await interaction.editReply(draftedPlayer);
} catch (error) {
console.log(error);
await interaction.editReply("Could not draft player");
}
}
and
async function getConfirmation(interaction, player) {
try {
// send confirmation msg
const message = await interaction.channel.send({
content: `Are you sure you want to draft ${formatPlayerString(
player
)}?`
});
const filter = (reaction, user) => {
return (
["👎", "👍"].includes(reaction.emoji.name) &&
user.id === interaction.user.id
);
};
// react to the msg to allow for easier reacting
await Promise.all([message.react("👍"), message.react("👎")]);
// awaitReactions just returns a list of all reaction objs that match above
// filter
let didConfirm, responseMessage;
message
.awaitReactions({
filter,
max: 1,
time: 60000,
errors: ["time"],
})
.then((collected) => {
const reaction = collected.first();
didConfirm = reaction.emoji.name === "👍";
responseMessage = didConfirm
? "Confirmed. Drafting now..."
: "Cancelled confirmation.";
})
.catch((collected) => {
console.log(collected);
responseMessage =
"Did not receive response in time. Abandoning...";
didConfirm = false;
})
.finally(async (collected) => {
// clean up after 3s to prevent clutter
setTimeout(() => message.delete(), 3000);
// resolve the promise
await message.edit(responseMessage);
return didConfirm;
});
} catch (error) {
console.log("Errored out : ", JSON.stringify(error));
console.log(error);
throw error;
}
}
Sorry for the large chunk of code but let me explain. I want the bot to send a confirmation message, listen for any reaction replies to the message, and then proceed with drafting the player if the user who sent the /draft command reacts with a thumbs up emoji, and abandons otherwise.
The issue is with the getConfirmation() method. Despite me having designated getConfirmation() as an async method, which should wrap my response in a promise and awaiting it, the code in execute() continues to run immediately after the await getConfirmation() line. It sets hasConfirmed = undefined and proceeds with that assumption. So no matter what the user reacts, the code will reach the !hasConfirmed block, and respond with "Draft choice abandoned".
However, this does not make any sense to me. Since getConfirmation() is an async method, and we are awaiting it in another async method, shouldn't the await keyword halt execution until the promise has been fulfilled and hasConfirmed has a value? The confirmation message sent in getConfirmation does get correctly updated when the user reacts though, so I know that that code is being reached. Am I missing something obvious? Do I just fundamentally misunderstand asynchronous programming?
I'd really appreciate any help! Thanks a lot.
Edit: Phil's answer was it
getConfirmation() doesn't return anything so will always resolve to undefined. FYI the return value from finally() is not used to resolve the promise chain
// Quick demo showing what happens with finally
Promise.resolve(undefined).finally(() => "finally").then(console.log);
It's also best not to mix async / await with .then() / .catch() as it can quickly get confusing.
I would instead structure the await reactions part of your code like this
let didConfirm, responseMessage;
try {
const collected = await message.awaitReactions({
filter,
max: 1,
time: 60000,
errors: ["time"],
});
const reaction = collected.first();
didConfirm = reaction.emoji.name === "👍";
responseMessage = didConfirm
? "Confirmed. Drafting now..."
: "Cancelled confirmation.";
} catch (err) {
console.warn(err);
responseMessage = "Did not receive response in time. Abandoning...";
didConfirm = false;
}
// Things after an async try..catch is similar to finally
// only you can actually return a value
// clean up after 3s to prevent clutter
setTimeout(() => message.delete(), 3000);
// resolve the promise
await message.edit(responseMessage);
return didConfirm;

Downside to abusing await as return statement?

I've stumbled upon an interesting but rather hacked way to write linear code with promises in Javascript, as follows:
const return_by_death = new Promise((resolve) => {});
and
const signup = async (req, res) => {
const user = req.body.username;
const pass = req.body.password;
if(!user || !pass) {
res.sendStatus(400);
return;
}
const hash_data = await generate_hash(pass).catch(async (err) => {
res.sendStatus(500);
await return_by_death;
});
const new_account = new models.account.model({
username: user,
salt: hash_data.salt,
hash: hash_data.hash
});
// ...
};
and from my experimentation, it seems to work in the way that if the promise from generate_hash is rejected, it will go into my error handler without moving on to the new_account line. My questions are as follows:
Does this waste memory, by generating promises or execution threads that just hang indefinitely?
Does expressjs hold on to functions passed into app.get(path, func) where it would be keeping track of the signup promise that might never resolve?
Is there a better way to do this?
EDIT: based on the answer/information from #CertainPerformance I came up with the following solution
class PromiseRunError {
constructor(obj) {
this.obj = obj;
}
}
Promise.prototype.run = function() {
return this.catch((err) => {return new PromiseRunError(err)}).then((data) => {
if(data instanceof PromiseRunError) {
return [undefined, data.obj];
}
return [data, undefined];
});
};
and
const [hash_data, hash_gen_err] = await generate_hash(pass).run();
if(hash_gen_err) {
res.sendStatus(500);
return;
}
Yes, using perpetually unresolved Promises here is a problem.
If signup gets called many times, and many Promises that hang are generated, more and more memory will get used over time; each call of signup results in a new closure with user, pass, req, and res variables allocated, which can't be garbage collected until the function ends. (But it'll never end if there's an error.)
It's extremely confusing from a readability standpoint.
If you want to keep things as flat as possible, without surrounding everything in a try/catch, assuming that generate_hash returns a Promise that, if it resolves, will resolve to something truthy, just don't return anything inside the .catch handler, then check if hash_data is truthy or not before continuing:
const signup = async (req, res) => {
const user = req.body.username;
const pass = req.body.password;
if(!user || !pass) {
res.sendStatus(400);
return;
}
const hash_data = await generate_hash(pass).catch(() => {});
if (!hash_data) {
res.sendStatus(500);
return;
}
const new_account = new models.account.model({
username: user,
salt: hash_data.salt,
hash: hash_data.hash
});
// ...
};
I'd prefer try/catch, though:
const signup = async (req, res) => {
try {
const { user, pass } = req.body;
if (!user || !pass) {
res.sendStatus(400);
return;
}
const hash_data = await generate_hash(pass)
const new_account = new models.account.model({
username: user,
salt: hash_data.salt,
hash: hash_data.hash
});
// ...
} catch (e) {
res.sendStatus(500);
return;
}
};

Code behaving differently in Production vs Locally

I have a Loopback app where I have created some seed scripts to pre-populate the db.
Here is the seed
const remote = {
"name": "remote",
"email": "remote#ttt.com",
"password": "arglebargle"
}
app.models.AppUser.find({where: {email: 'remoteUser#ttt.com'}})
.then(res => {
if (res.length === 0) {
createUsers(remote, 'remote')
} else {
console.log('remote user already exists')
}
})
This calls createUsers which is below
const app = require('../server')
const Promise = require('bluebird');
module.exports = {
createUsers: (userInfo, roleName) => {
if (!userInfo || !roleName) {
let err = new Error('please give valid user and role names')
console.log(err)
return err
}
console.log(userInfo)
return app.models.AppUser.findOrCreate({where: {'name': userInfo.name}}, userInfo)
.then((instance) => {
return app.models.Role.findOne({where: {name: roleName}})
.then((role) => {
return role.principals.create({
principalType: app.models.RoleMapping.USER,
principalId: instance[0].id //find or create returns an array
})
})
})
.catch((error) => {
return error
})
}
}
The above probably isn't good promise based code. But this is working fine in other situations so I am not sure if I can blame it.
Running the above script creates the 'remote' user and assigns the 'remote' role to it locally, however it does not do anything in production and I just cannot figure out why.
The only other difference I can think of between production and local is that the I am calling them from different locations (the project root is different)
I see a couple issues here. First, in this block:
createUsers: (userInfo, roleName) => {
if (!userInfo || !roleName) {
let err = new Error('please give valid user and role names')
console.log(err)
return err
}
console.log(userInfo)
return app.models.AppUser.findOrCreate
you're returning an Error and a Promise from one function. Then here:
if (res.length === 0) {
createUsers(remote, 'remote')
} else {
console.log('remote user already exists')
}
you're ignoring the return value altogether from createUsers
I'd stick with promises, as that seems to be the direction you're going, like so:
createUsers: (userInfo, roleName) => {
if (!userInfo || !roleName) {
let err = new Error('please give valid user and role names')
console.log(err)
return Promise.reject(err)
}
...
and you must handle every promise, otherwise it will silently fail.
if (res.length === 0) {
createUsers(remote, 'remote')
.then(result => console.log('got a result'))
.catch(error => console.error('oh no!', error))
} else ...
And also, the first AppUser.find is not being handled if there is an error:
app.models.AppUser.find({where: {email: 'remoteUser#ttt.com'}})
.catch(err => console.error('yikes!', err))
.then(res => {
The code you have now will silently swallow any errors that occur in createUsers or that first call. So, something like the database being unreachable in production would never present itself.
Hope this helps!

Break out of Bluebird promise chain in Mongoose

I've studied several related questions & answers and still can't find the solution for what I'm trying to do. I'm using Mongoose with Bluebird for promises.
My promise chain involves 3 parts:
Get user 1 by username
If user 1 was found, get user 2 by username
If both user 1 and user 2 were found, store a new record
If either step 1 or step 2 fail to return a user, I don't want to do step 3. Failing to return a user, however, does not cause a database error, so I need to check for a valid user manually.
I can use Promise.reject() in step 1 and it will skip step 2, but will still execute step 3. Other answers suggest using cancel(), but I can't seem to make that work either.
My code is below. (My function User.findByName() returns a promise.)
var fromU,toU;
User.findByName('robfake').then((doc)=>{
if (doc){
fromU = doc;
return User.findByName('bobbyfake');
} else {
console.log('user1');
return Promise.reject('user1 not found');
}
},(err)=>{
console.log(err);
}).then((doc)=>{
if (doc){
toU = doc;
var record = new LedgerRecord({
transactionDate: Date.now(),
fromUser: fromU,
toUser: toU,
});
return record.save()
} else {
console.log('user2');
return Promise.reject('user2 not found');
}
},(err)=>{
console.log(err);
}).then((doc)=>{
if (doc){
console.log('saved');
} else {
console.log('new record not saved')
}
},(err)=>{
console.log(err);
});
Example
All you need to do is something like this:
let findUserOrFail = name =>
User.findByName(name).then(v => v || Promise.reject('not found'));
Promise.all(['robfake', 'bobbyfake'].map(findUserOrFail)).then(users => {
var record = new LedgerRecord({
transactionDate: Date.now(),
fromUser: users[0],
toUser: users[1],
});
return record.save();
}).then(result => {
// result of successful save
}).catch(err => {
// handle errors - both for users and for save
});
More info
You can create a function:
let findUserOrFail = name =>
User.findByName(name).then(v => v || Promise.reject('not found'));
and then you can use it like you want.
E.g. you can do:
Promise.all([user1, user1].map(findUserOrFail)).then(users => {
// you have both users
}).catch(err => {
// you don't have both users
});
That way will be faster because you don't have to wait for the first user to get the second one - both can be queried in parallel - and you can scale it to more users in the future:
let array = ['array', 'with', '20', 'users'];
Promise.all(array.map(findUserOrFail)).then(users => {
// you have all users
}).catch(err => {
// you don't have all users
});
No need to complicate it more than that.
move your error handling out of the inner chain to the place you want to actual catch/handle it. As i don't have mongo installed, here is some pseudocode that should do the trick:
function findUser1(){
return Promise.resolve({
user: 1
});
}
function findUser2(){
return Promise.resolve({
user: 2
});
}
function createRecord(user1, user2){
return Promise.resolve({
fromUser: user1,
toUser: user2,
});
}
findUser1()
.then(user1 => findUser2()
.then(user2 => createRecord(user1, user2))) // better nest your promises as having variables in your outside scope
.then(record => console.log('record created'))
.catch(err => console.log(err)); // error is passed to here, every then chain until here gets ignored
Try it by changing findUser1 to
return Promise.reject('not found 1');
First, I would recommend using throw x; instead of return Promise.reject(x);, simply for readibility reasons. Second, your error logging functions catch all the errors, that's why your promise chain is continuing. Try rethrowing the errors:
console.log(err);
throw err;
Don't put error logging everywhere without actually handling the error - if you pass an error handler callback you'll get back a promise that will fulfill with undefined, which is not what you can need. Just use
User.findByName('robfake').then(fromUser => {
if (fromUser) {
return User.findByName('bobbyfake').then(toUser => {
if (toUser) {
var record = new LedgerRecord({
transactionDate: Date.now(),
fromUser,
toUser
});
return record.save()
} else {
console.log('user2 not found');
}
});
} else {
console.log('user1 not found');
}
}).then(doc => {
if (doc) {
console.log('saved', doc);
} else {
console.log('saved nothing')
}
}, err => {
console.error("something really bad happened somewhere in the chain", err);
});
This will always log one of the "saved" or "something bad" messages, and possibly one of the "not found" messages before.
You can also use exceptions to achieve this, but it doesn't really get simpler:
var user1 = User.findByName('robfake').then(fromUser => {
if (fromUser)
return fromUser;
else
throw new Error('user1 not found');
});
var user2 = user1.then(() => // omit this if you want them to be searched in parallel
User.findByName('bobbyfake').then(toUser => {
if (toUser)
return toUser;
else
throw new Error('user2 not found');
})
);
Promise.all([user1, user2]).then([fromUser, toUser]) =>
var record = new LedgerRecord({
transactionDate: Date.now(),
fromUser,
toUser
});
return record.save();
}).then(doc => {
if (doc) {
console.log('saved', doc);
} else {
console.log('saved nothing')
}
}, err => {
console.error(err.message);
});

Categories

Resources