Reputation: 3479
I have an async method in a controller that sets a results object. The problem is instead of waiting for the await
to finish executing my code is jumping to the response object call which leaves the needed variable undefined. In the debugger breakpoints in the method that's supposed to be executed are hit after the undefined error. Can anyone explain why async await isn't working here?
method in controller class:
public async loginUser(req: Request, res: Response) {
const { name, password } = req.body;
let result: ILoginResult = await UserData.login(name, password); // always undefined
res.status(result.status).send(result.result); // gets hit before result is set
}
UserData class:
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import mongoose from 'mongoose';
import ILoginResult from './ILoginResult';
import UserModel from '../../models/UserModel';
class UserData {
private connUri: string;
constructor() {
this.connUri = process.env.MONGO_LOCAL_CONN_URL;
}
public async login(name: string, password: string) {
try {
await mongoose.connect(this.connUri, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, }, (err) => {
let result: ILoginResult = { status: 0, result: null, error: '', token: '' };
let status = 200;
if (!err) {
UserModel.findOne({ name }, (err, user) => {
if (!err && user) {
// We could compare passwords in our model instead of below as well
bcrypt.compare(password, user.password).then(match => {
if (match) {
status = 200;
// Create a token
const payload = { user: user.name };
const options = { expiresIn: '2d', issuer: 'http://localhost' };
const secret = process.env.JWT_SECRET;
const token = jwt.sign(payload, secret, options);
// console.log('TOKEN', token);
result.token = token;
result.status = status;
result.result = user;
} else {
status = 401;
result.status = status;
result.error = `Authentication error`;
}
return result;
}).catch(err => {
status = 500;
result.status = status;
result.error = err;
return { status: status, result: result };
});
} else {
status = 404;
result.status = status;
result.error = err;
return result;
}
});
} else {
status = 500;
result.status = status;
result.error = err.toString();
return result;
}
});
} catch (e) {
let result: ILoginResult;
result.error = e.toString();
result.status = 500;
return result;
}
}
}
export default new UserData();
Upvotes: 0
Views: 1256
Reputation: 11080
await
doesn't work on traditional node-style callbacks. It only works on Promises. Since you're providing a callback, the callback will be executed asynchronously and not waited for by the await
.
Besides, using a callback when you're already using await
and the function returns a promise really defeats the purpose of even using await
. Write the code without callbacks:
let result: ILoginResult = { status: 0, result: null, error: '', token: '' };
try {
await mongoose.connect(this.connUri, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, });
let status = 200;
let user = await UserModel.findOne({ name });
if (user) {
// We could compare passwords in our model instead of below as well
let match = await bcrypt.compare(password, user.password);
if (match) {
status = 200;
// Create a token
const payload = { user: user.name };
const options = { expiresIn: '2d', issuer: 'http://localhost' };
const secret = process.env.JWT_SECRET;
const token = jwt.sign(payload, secret, options);
// console.log('TOKEN', token);
result.token = token;
result.status = status;
result.result = user;
} else {
status = 401;
result.status = status;
result.error = `Authentication error`;
}
return result;
} else {
status = 404;
result.status = 404;
result.error = err;
return result;
}
} catch (e) {
result.error = e.toString();
result.status = 500;
return result;
}
To me this is way cleaner, less of the callback trees from hell and more elegant usage of async/await. Since the whole thing is wrapped in try/catch, if any of the await ...
s error, the outer catch will grab it and return your standard error 500 with message instead of needing to repeat that code many times within.
Upvotes: 1
Reputation: 1075517
Don't mix async
/await
directly with callback-based APIs.
According to the documentation, mongoose.connect
does return a promise (provided you're using an up-to-date version), but there's nothing to suggest it makes that promise wait for things happening in the callback you give it.
Instead, do those things in the code following the await mongoose.connect
.
Something along these lines (in UserData
):
public async login(name: string, password: string) {
let result: ILoginResult = { status: 0, result: null, error: '', token: '' };
try {
await mongoose.connect(this.connUri, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, });
const user = await UserModel.findOne({ name });
let status = 200;
let match = await bcrypt.compare(password, user.password).catch(() => false);
if (!match) {
status = 401;
result.status = status;
result.error = `Authentication error`;
return result;
}
status = 200;
// Create a token
const payload = { user: user.name };
const options = { expiresIn: '2d', issuer: 'http://localhost' };
const secret = process.env.JWT_SECRET;
const token = jwt.sign(payload, secret, options);
// console.log('TOKEN', token);
result.token = token;
result.status = status;
result.result = user;
return result;
} catch (e) {
result.error = e.toString();
result.status = 500;
return result;
}
}
Upvotes: 1