Reputation: 2193
I've created a simple user class with a username, hashedpassword and passwordsalt. Because the hashing and salting of takes some time, I decided to create a static async function to create a new user (Asynchronous constructor). But somehow when I call this static async function to create a new user, the await doesn't wait for the createUser to finish. Does anybody know why?
console.log(1);
var newUser = await User.createUser(newUserJson);
console.log(3);
console.log(newUser.getPasswordHash()); //<- this line fails with the error that it cannot do the .getPasswordHash on undefined.
static async createUser(jsonUser) {
var password = jsonUser.password;
const saltRounds = 10;
try {
bcrypt.genSalt(saltRounds, function(err, salt) {
bcrypt.hash(password, salt, function(err, hash) {
jsonUser.passwordHash = hash;
jsonUser.passwordSalt = salt;
var us = new User(jsonUser); //nonblocking. Just sets some fields.
console.log(2);
return us;
});
})
} catch (err) {
console.log('error')
console.log(err)
}
}
My console output shows 1, 3, 2...
Does anybody know why newUser.getPasswordHash()
doesn't wait for var newUser = await User.createUser(newUserJson)
; ?
Upvotes: 1
Views: 3858
Reputation: 151132
This pretty much sums up the confusion on async/await
. You seem to think you need to make something as async
in order to await
it. It's actually the opposite, since you mark as async
in order to "contain" methods which you intend to await
. So as said, you need bcrypt.genSalt
and bcrypt.hash
both to be wrapped in a Promise
.
There's libraries to do that, or you can just roll it with the "vanilla" functions already included. And then of course you are trapping exceptions in the wrong place as well:
static createUser(jsonUser) {
const { password } = jsonUser;
const saltRounds = 10;
return new Promise((resolve,reject) =>
bcrypt.genSalt(saltRounds, (err, salt) => {
if (err) reject(err);
resolve(salt);
})
).then(salt => new Promise((resolve,reject) =>
bcrypt.hash(password, salt, (err, hash) => {
if (err) reject(err);
jsonUser.passwordHash = hash;
jsonUser.passwordSalt = salt;
var us = new User(jsonUser);
resolve(us);
})
);
}
Or if you "really want" to use an await
instead of chaining the promises with .then()
, that's when you mark with async
:
static async createUser(jsonUser) { // marked as async
const { password } = jsonUser;
const saltRounds = 10;
let salt = await new Promise((resolve,reject) => // because we await here
bcrypt.genSalt(saltRounds, (err, salt) => {
if (err) reject(err);
resolve(salt);
})
);
return new Promise((resolve,reject) =>
bcrypt.hash(password, salt, (err, hash) => {
if (err) reject(err);
jsonUser.passwordHash = hash;
jsonUser.passwordSalt = salt;
var us = new User(jsonUser);
resolve(us);
})
);
}
So it's just defining a function that returns a Promise
, and nothing more magical than that.
Then on course you actually use your await
to get the value "within" a block which is marked async
, and of course do the exception handling there instead. Juts using an IIFE here for demonstration:
(async function() { // async marks the block
try {
console.log(1);
var newUser = await User.createUser(newUserJson); // where you can await
console.log(3);
console.log(newUser.getPasswordHash()); // This is expected to be NOT async
} catch(e) {
console.error(e);
}
})()
So there is nothing magic here at all, and the async/await
is simply "sugar" for calling .then()
to get the resolved value of a Promise
. But the functions themselves simply are are a Promise
to return, and you only mark as async
where the "inner block" of code actually calls an await
.
Follow that and your fine, and of course if the User.getPasswordHash()
was actually an "async" function itself, then you similarly want to return a Promise
from that and then await
the result:
console.log(await newUser.getPasswordHash());
But if new User()
is not async, then you likely don't have async sub-methods of that.
Upvotes: 2