Downside to abusing await as return statement? - javascript

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

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

How to pause between two asynchronous actions?

I need to make pause between "/system/backup/save" and "/file/print". Because otherwise, the backup will not be completed before the contents of the "/file" directory are displayed.
Now the code is performing a backup, but it gives me a list of files in which there is no backup yet.
const RouterOSAPI = require("node-routeros").RouterOSAPI;
const sleep = require('util').promisify(setTimeout);
var hosts = require('./config.json');
async function backup() {
return new Promise(function (resolve, reject) {
for (let elem of hosts) {
const conn = new RouterOSAPI({
host: elem.host,
user: elem.user,
password: elem.password
})
conn.connect()
.then((client) => {
return conn.write(["/system/backup/save",]).then((data) => {
resolve('COMPLETE - OK');
}).catch((err) => {
reject('ERROR!');
});
sleep(5000);
}).then(() => {
return conn.write("/file/print").then((data2) => {
console.log(data2)
resolve('CHECK - OK');
conn.close();
}).catch((err) => {
reject('ERROR!');
});
}).catch((err) => {
reject('ERROR CONNECT TO ' + elem.name);
});
}
});
}
backup();
Generally, using a delay to wait for completion of an asynchronous process is an anti-pattern  you'll always end up either not waiting long enough or waiting an unnecessarily long time. The former is of course a bigger problem than the latter, but both are problems. If you have any means of having the other end report completion of the backup, that would be your best bet. Looking at the documentation, it seems like conn.write's promise shouldn't be fulfilled until the operation is complete, but I only skimmed the docs so maybe that's not the case.
Other than that:
Don't create the promise explicitly, your async function automatically creates a promise (but you may not want an async function here anyway)
Don't mix using .then/.catch handlers with async functions; use await.
For instance, here's a version that runs the backups and such in parallel and returns an array giving success/failure via allSettled:
const RouterOSAPI = require("node-routeros").RouterOSAPI;
const sleep = require('util').promisify(setTimeout);
var hosts = require('./config.json');
async function backup() {
// Run the hosts in parallel
return await Promise.allSettled(hosts.map(async (host) => {
let conn;
try {
const c = new RouterOSAPI({
host: elem.host,
user: elem.user,
password: elem.password
})
const client = await c.connect();
conn = c;
await conn.write(["/system/backup/save",]);
await sleep(5000); // Only if really unavoidable because the
// backup continues even after the promise
// from `write` is fulfilled
await conn.write("/file/print");
conn = null;
c.close();
} catch (e) {
if (conn) {
try {
conn.close();
} catch {} // Don't let errors in close shadow previous errors
}
throw e;
}
}));
}
backup()
.then(results => {
// Check for status = "rejected" entries in results and report the errors
});
But note that since that function just returns the promise from allSettled, you might not want an async function at all:
const RouterOSAPI = require("node-routeros").RouterOSAPI;
const sleep = require('util').promisify(setTimeout);
var hosts = require('./config.json');
function backup() {
// Run the hosts in parallel
return Promise.allSettled(hosts.map(async (host) => {
let conn;
try {
const c = new RouterOSAPI({
host: elem.host,
user: elem.user,
password: elem.password
})
const client = await c.connect();
conn = c;
await conn.write(["/system/backup/save",]);
await sleep(5000); // Only if really unavoidable because the
// backup continues even after the promise
// from `write` is fulfilled
await conn.write("/file/print");
conn = null;
c.close();
} catch (e) {
if (conn) {
try {
conn.close();
} catch {} // Don't let errors in close shadow previous errors
}
throw e;
}
}));
}
backup()
.then(results => {
// Check for status = "rejected" entries in results and report the errors
});
(There's a subtle difference between those two around what happens if hosts.map throws an error — perhaps because hosts isn't an array — but it's probably not important. The former returns a rejected promise, the latter throws a synchronous error.)

How to access the body of a request outside in an async function?

So I have a post route which is an async await function, and I have a reuest inside it which pulls some data from the api and I want to save the content of that body in a variable outside the request function
I have tried using promises but I am not really familiar with that.
//#route POST api/portfolio/stock
//#desc Create or update a user's stock portfolio
//#access private
router.post(
"/stock",
auth,
[
check("symbol", "symbol is require or incorrect.")
.not()
.isEmpty(),
check("qty", "Quantity of the stock purchased is required")
.not()
.isEmpty()
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty) {
return res.status(400).json({ errors: errors.array() });
}
const { stock, qty } = req.body;
const newPortfolio = {};
newPortfolio.user = req.user.id;
newPortfolio.stocks = [];
if(stock) newPortfolio.stocks.stock = stock;
if(qty) newPortfolio.stocks.qty = qty;
request(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${stock}&apikey=${config.get(
"API_KEY")}`, (error, response, body) => {
if (error) console.error(error);
if (response.statusCode !== 200) {
res.status(404).json({msg: 'No stock found'});
}
let content = JSON.parse(body);
let quote = content['Global Quote'];
});
newPortfolio.stocks.stockInfo = quote;
try {
let portfolio = await Portfolio.findOne({ user: user.req.id });
//update if exists
if(portfolio) {
portfolio = await Portfolio.findOneAndUpdate(
{ user: user.req.id },
{ $push: { stocks: newPortfolio.stocks }}
);
return res.json(portfolio);
}
//create if not found
portfolio = new Portfolio(newPortfolio);
await portfolio.save();
res.json(portfolio);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
I want to save myPortfolio.stocks.stockInfo using body of that request.
How to access the body of a request outside in an async function?
You don't. Asynchronous results are only available in the asynchronous context. For an asynchronous function that returns the result via a callback, that means you consume/use the results INSIDE the callback. Any code that needs access to those results goes in the callback. In traditional async Javascript programming, you just continue the code of your function inside the callback.
Fortunately, promises and the invention of async and await can make the coding a bit simpler. For asynchronous functions that return a promise (instead of taking a callback), you can use await to get their result and you can write code that looks more like the sequential model, even though it's still asynchronous.
For example, this is what a rewrite of your function might look like where we switch the request-promise library (same as the request library, but returns a promise instead of uses a callback) and then we use await on the result:
const rp = require('request-promise');
router.post(
"/stock",
auth,
[
check("symbol", "symbol is require or incorrect.")
.not()
.isEmpty(),
check("qty", "Quantity of the stock purchased is required")
.not()
.isEmpty()
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty) {
return res.status(400).json({
errors: errors.array()
});
}
const {
stock,
qty
} = req.body;
const newPortfolio = {};
newPortfolio.user = req.user.id;
newPortfolio.stocks = [];
if (stock) newPortfolio.stocks.stock = stock;
if (qty) newPortfolio.stocks.qty = qty;
try {
let body = await rp(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${stock}&apikey=${config.get("API_KEY")}`);
let content = JSON.parse(body);
newPortfolio.stocks.stockInfo = content['Global Quote'];
} catch (e) {
console.error(e);
res.status(404).json({
msg: 'No stock found'
});
return;
}
try {
let portfolio = await Portfolio.findOne({
user: user.req.id
});
//update if exists
if (portfolio) {
portfolio = await Portfolio.findOneAndUpdate({
user: user.req.id
}, {
$push: {
stocks: newPortfolio.stocks
}
});
return res.json(portfolio);
}
//create if not found
portfolio = new Portfolio(newPortfolio);
await portfolio.save();
res.json(portfolio);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
Note: your adding properties to newPortfolio.stocks which is an array is quite unusual. While it's not technically wrong, you would usually declare newPortfolio.stocks to be an object, not an array if you were just going to add properties to it and not use it like an actual array. It can often be confusing to people reading your code if you use the same variable as both an object and an array. Usually, you want a variable (or property) to behave as one or the other, not both.

Async function in mongoose pre save hook not working

Calling an async function in a pre save hook is returning me undefined for the password. Am I fundamentally misunderstanding async here? I've used it successfully in other areas of my app an it seems to be working fine...
userSchema.pre('save', function (next) {
let user = this
const saltRounds = 10;
const password = hashPassword(user)
user.password = password;
next();
})
async hashPassword(user) {
let newHash = await bcrypt.hash(password, saltRounds, function(err, hash) {
if (err) {
console.log(err)
}
return hash
});
return newHash
}
I think you'd need to handle the promise returned by hashPassword:
hashPassword(user)
.then(password => {user.password = password
next()}
)
I don't think you can turn userSchema.pre into an async function.
Mongoose hooks allow async functions in them. It worked for me. Note that the "next" callback parameter is not needed in async functions as the function executes synchronously.
Here is what a correct async/await version of the code posted in the question would be. Please note that the corrections are marked in bold:
userSchema.pre('save', async function () {
let user = this;
const saltRounds = 10;
const hashed_password = await hashPassword(user.password, saltRounds);
user.password = hashed_password;
}); // pre save hook ends here
async hashPassword(password, saltRounds) {
try {
let newHash = await bcrypt.hash(password, saltRounds);
} catch(err){
// error handling here
}
return newHash;
}
Just to clean things up a bit:
userSchema.pre('save', function(next) {
if (!this.isModified('password')) {
return next();
}
this.hashPassword(this.password)
.then((password) => {
this.password = password;
next();
});
});
userSchema.methods = {
hashPassword(password) {
return bcrypt.hash(password, 10);
},
}
let user = this can be dropped when using an arrow function in then.
When using bcrypt.hash() with no callback, it returns a promise.
async for hashPassword is redundant when using .then when calling

Categories

Resources