Code behaving differently in Production vs Locally - javascript

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!

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 =>

Execute code synchronously in node.js controller

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().

Cloud functions not firing, browser window also freezes when trying to view logs

My cloud functions are still using node 8, can't update to node 10 just yet
I am expecting my cloud function to fire once there is an update on the user doc
exports.updateUser = functions.firestore.document('users/{userId}').onUpdate((change, context) => {
const after = change.after.data()
if (after.status === 'VERIFIED') {
console.log('EMAIL IS VERIFIED')
}
if (after.isVerified) {
console.info(JSON.stringify(after))
admin
.auth()
.getUser(after.uid)
.then(() => {
console.log('Email is verified')
const metadataRef = admin.database().ref('metadata/' + userRecord.uid)
return metadataRef.set({ refreshTime: new Date().getTime() })
})
.catch(error => {
console.log(error)
})
}
return
})
I had this working in the past, but not sure why it's not working now.
Right now the function is not even being called, and when I click 'view logs' in the firebase dashboard my whole browser window freezes - i'm not sure if this is related to my functions still using node 8
I can also confirm that the email is verified after clicking the link sent to my email using
firebase
.auth()
.createUserWithEmailAndPassword(inputs.email, inputs.password)
.then(res => {
const userUid = res.user.uid
const db = firebase.firestore()
// setUser(userUid)
db.collection('/users')
.doc(userUid)
.set({
emailAddress: inputs.email,
uid: userUid,
})
res.user.sendEmailVerification()
})
.catch(err => {
setErrors(prev => [...prev, err.message])
})
}
As well as logging in, accessing the user and emailVerfied is set to true
Your Cloud Function code is going to generate some "erratic behaviour", which typically results in what you experienced ("I had this working in the past, but not sure why it's not working now").
You need to terminate a Cloud Function when all the asynchronous work is completed, see the doc. In the case of a background triggered Cloud Function you need to return the chain of promises returned by the asynchronous method calls.
In your case, not only you don't return the promise returned by getUser() but you have a return at the end that may indicate to the Cloud Function platform that it can clean up your function before the asynchronous work is completed.
The following should do the trick (untested):
exports.updateUser = functions.firestore.document('users/{userId}').onUpdate((change, context) => {
const after = change.after.data()
if (after.status === 'VERIFIED') {
console.log('EMAIL IS VERIFIED')
}
if (after.isVerified) {
console.info(JSON.stringify(after))
return admin
.auth()
.getUser(after.uid)
.then(() => {
console.log('Email is verified')
const metadataRef = admin.database().ref('metadata/' + userRecord.uid)
return metadataRef.set({ refreshTime: new Date().getTime() })
})
.catch(error => {
console.log(error)
return null;
})
} else {
return null;
}
});

Catching promise errors inside another promise's callback

The code below runs as expected. If the charge function is invoked, the function fetches the relevant ticket object from firestore and then returns it back to the client.
If the ticket doesn't exist, the function throws a HttpsError with an error message that will be parsed by the client.
exports.charge = functions.https.onCall(data => {
return admin.firestore().collection('tickets').doc(data.ticketId.toString()).get()
.then((snapshot) => {
return { ticket: snapshot.data() }
})
.catch((err) => {
throw new functions.https.HttpsError(
'not-found', // code
'The ticket wasn\'t found in the database'
);
});
});
The problem comes after this. I now need to charge the user using Stripe, which is another asynchronous process that will return a Promise. The charge requires the pricing info obtained by the first async method, so this needs to be called after snapshot is retrieved.
exports.charge = functions.https.onCall(data => {
return admin.firestore().collection('tickets').doc(data.ticketId.toString()).get()
.then((snapshot) => {
return stripe.charges.create(charge) // have removed this variable as irrelevant for question
.then(() => {
return { success: true };
})
.catch(() => {
throw new functions.https.HttpsError(
'aborted', // code
'The charge failed'
);
})
})
.catch(() => {
throw new functions.https.HttpsError(
'not-found', // code
'The ticket wasn\'t found in the database'
);
});
});
My problem is with catching errors in the new charge request. It seems that if the charge fails, it successfully calls the first 'aborted' catch, but then it is passed to parent catch, and the error is overridden and the app sees the 'ticket not found' error.
How can I stop this from happening? I need to catch both errors separately and throw a HttpsError for each one.
Generally, such problems are can be handled with adding status node and then chaining with a final then block. You can try something like following
exports.charge = functions.https.onCall(data => {
return admin.firestore().collection('tickets').doc(data.ticketId.toString()).get()
.then((snapshot) => {
return stripe.charges.create(charge)
.then(() => {
return { success: true };
})
.catch(() => {
return {
status : 'error',
error : new functions.https.HttpsError(
'aborted', // code
'The charge failed',
{ message: 'There was a problem trying to charge your card. You have NOT been charged.' }
)};
})
})
.catch(() => {
return {
status : 'error',
error : new functions.https.HttpsError(
'not-found', // code
'The ticket wasn\'t found in the database',
{ message: 'There was a problem finding that ticket in our database. Please contact support if this problem persists. You have NOT been charged.' }
)};
}).then((response) => {
if(response.status === 'error') throw response.error;
else return response;
});
});
Don't nest a then inside another then for multiple items of work:
work1
.then((work1_results) => {
return work2.then((work2_results) => {
// this is bad
})
})
Instead, perform all your work as a chained sequence:
work1
.then((work1_results) => {
return work2
})
.then((work2_results) => {
// handle the results of work2 here
})
You can store intermediate results in higher-scoped variables if you need to accumulate data between your callbacks.

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