This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
I have a function inside an if statement
isLoggedin() has an async call.
router.get('/', function(req, res, next) {
if(req.isLoggedin()){ <- never returns true
console.log('Authenticated!');
} else {
console.log('Unauthenticated');
}
});
how do i await for isLoggedin() in this if statement?
here is my isLoggedin function in which im using passport
app.use(function (req, res, next) {
req.isLoggedin = () => {
//passport-local
if(req.isAuthenticated()) return true;
//http-bearer
passport.authenticate('bearer-login',(err, user) => {
if (err) throw err;
if (!user) return false;
return true;
})(req, res);
};
next();
});
I do this exact thing using async/await in my games code here
Assuming req.isLoggedIn() returns a boolean, it's as simple as:
const isLoggedIn = await req.isLoggedIn();
if (isLoggedIn) {
// do login stuff
}
Or shorthand it to:
if (await req.isLoggedIn()) {
// do stuff
}
Make sure you have that inside an async function though!
You could promisify your function, like this:
req.isLoggedin = () => new Promise((resolve, reject) => {
//passport-local
if(req.isAuthenticated()) return resolve(true);
//http-bearer
passport.authenticate('bearer-login', (err, user) => {
if (err) return reject(err);
resolve(!!user);
})(req, res);
});
And then you can do:
req.isLoggedin().then( isLoggedin => {
if (isLoggedin) {
console.log('user is logged in');
}
}).catch( err => {
console.log('there was an error:', err);
});
Do not try to keep the synchronous pattern (if (req.isLoggeedin())), as it will lead to poorly designed code. Instead, embrace fully the asynchronous coding patterns: anything is possible with it.
Related
Background: The simplified test code below uses Express and Mongoose.
Question: I set up the .then statement to throw an error for testing. When an exception is thrown my error handling middleware is triggered with next() but not before res.render('index', { doesUserExist }); is hit. This line results in the error, "Cannot set headers after they are sent to the client" because in my error handling middleware res.render('error_page', { err }); is also called. What part of my code should I change to eliminate the error?
Followup: Do I need more than a slight shift in my approach? Am I using the completely wrong pattern to perform this action efficiently/effectively?
app.get('/', function(req, res, next) {
(async function() {
let doesUserExist = await User.exists( { name: 'steve' })
.then( function(result) {
throw 'simulated error';
})
.catch( function(error) {
next(new Error(error));
});
res.render('index', { doesUserExist });
})();
});
app.use(function(err, req, res, next) {
res.render('error_page', { err });
});
This is because of an async function without a catch block
app.get('/', function (req, res, next) {
(async function () {
try {
let doesUserExist = await User.exists( { name: 'steve' });
if (doesUserExist) {
throw 'simulated error';
} else {
next(new Error(error));
}
res.render('index', { doesUserExist });
} catch (err) {
return next(err)
}
})();
});
app.use(function (err, req, res, next) {
res.render('error_page', { err });
});
Instead of next write return next(new Error(error)). In this way it wont execute any further code and go to the error middleware
You can create a function wrapper to catch all the errors and send them to the error middleware:
const asyncWrap = fn =>
function asyncUtilWrap (req, res, next, ...args) {
const fnReturn = fn(req, res, next, ...args)
return Promise.resolve(fnReturn).catch(next)
}
Then you can reutilize it in all your controllers, making the app much cleaner:
app.get('/', asyncWrap(async function(req, res, next) {
let doesUserExist = await User.exists( { name: 'steve' }) //*
.then( function(result) {
throw 'simulated error'; // This error is automatically sent to next()
})
.catch( function(error) {
next(new Error(error)); // This error works normally
});
res.render('index', { doesUserExist });
});
*You shouldnt combine await and then/catch syntax by the way.
passport.authenticate(), how can I define a Promise instead of using a Custom Ballback?
How to used passport.authenticate() is referenced within here:
http://www.passportjs.org/docs/authenticate/
Within this page, there is a section Custom Ballback:
If the built-in options are not sufficient for handling an authentication request, a custom callback can be provided to allow the application to handle success or failure.
app.get('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
The Custom Callback is defined as:
function(err, user, info){...}
What I wish to do is replace this Custom Callback with a Promise.
[Promise](resolve, reject)
.then(res => {
})
.catch(err => {
})
How can I do this? Thank you.
You can use the es6-promisify package. It is very easy to use, here is an example:
const {promisify} = require("es6-promisify");
// Convert the stat function
const fs = require("fs");
const stat = promisify(fs.stat);
// Now usable as a promise!
stat("example.txt").then(function (stats) {
console.log("Got stats", stats);
}).catch(function (err) {
console.error("Yikes!", err);
});
Thanks all for your helpful responses #sterling-archer and #el-finito
I had found a related issue within Passport.js Github repository helpful for using Passport to handle passport.authenticate() callback:
"Using node's promisify with passport"
export const authenticate = (req, res) =>
new Promise((resolve, reject) => {
passport.authenticate(
[passport strategy],
{ session: false },
(err, user) => {
if (err) reject(new Error(err))
else if (!user) reject(new Error('Not authenticated'))
resolve(user)
})(req, res)
})
In express, I have the following code:
app.get('/hello', (req, res) => {
return controller.doSomthingAsync(req).then(() => {
res.send({message: "Hello World"});
})
});
However, I can also do
app.get('/hello', (req, res) => {
controller.doSomthingAsync(req).then(() => { // Return removed
res.send({message: "Hello World"});
})
});
The above also works, but I was wondering if there is any difference between the 2 approaches, and can the second one cause any problems?
Considering the piece of code you provided, there is no difference in using the two versions.
The only benefit of that return is that any piece of code you put after that would not be executed. For instance, suppose the following piece of code:
app.get('/hello', (req, res) => {
if (req.query.foo === 'bar') {
controller.doSomethingAsync(req).then(() => {
res.send({message: "Hello World"});
}).catch(err => {
console.log(err);
res.status(500).send(err);
});
}
controller.doSomethingElseAsync(req).then(() => {
res.send({message: "Hello Universe"});
}).catch(err => {
console.log(err);
res.status(500).send(err);
});
});
This would produce an error since both of the async operations would be performed and try to send the response.
Adding a return in the if block would prevent executing the second async operation and the error.
app.get('/hello', (req, res) => {
if (req.query.foo === 'bar') {
return controller.doSomethingAsync(req).then(() => {
res.send({message: "Hello World"});
}).catch(err => {
console.log(err);
res.status(500).send("Error");
});
}
controller.doSomethingElseAsync(req).then(() => {
res.send({message: "Hello Universe"});
}).catch(err => {
console.log(err);
res.status(500).send("Error");
});
});
EDIT: As Bergi pointed out, using else would do the job as well and avoid to return something that express can't handle.
app.get('/hello', (req, res) => {
if (req.query.foo === 'bar') {
controller.doSomethingAsync(req).then(() => {
res.send({message: "Hello World"});
}).catch(err => {
console.log(err);
res.status(500).send("Error");
});
} else {
controller.doSomethingElseAsync(req).then(() => {
res.send({message: "Hello Universe"});
}).catch(err => {
console.log(err);
res.status(500).send("Error");
});
}
});
No, you don't need to return a promise from your express route, as express does not know what to do with promises (a pity!).
However, every time you call a promise-returning function and do not do further chaining and returning it to your caller, i.e. when you end a promise chain, you are responsible for handling errors from it. So it would be appropriate to do
app.get('/hello', (req, res) => {
controller.getMessageAsync(req).then(msg => {
res.status(200).send(msg);
}, err => {
console.error(err);
res.status(500).send("Sorry.");
});
});
I have a sequelize database that validates data and throws errors.
I know I can do something like this to catch and output my errors:
User.build()
.catch(Sequelize.ValidationError, function (err) {
// respond with validation errors
return res.status(422).send(err.errors);
})
.catch(function (err) {
// every other error
return res.status(400).send({
message: err.message
});
But I don't want to add it to every single request, is there some generic way to catch theese errors?
You can add a custom method to req (or res) that will resolve the promise and handle any errors:
app.use((req, res, next) => {
req.resolve = (promise) => {
return promise.catch(Sequelize.ValidationError, err => {
// respond with validation errors
return res.status(422).send(err.errors);
}).catch(err => {
// every other error
return res.status(400).send({ message: err.message });
});
});
next();
});
Usage (provided that the middleware above is added before your routes):
router.post('/user', (req, res) => {
req.resolve(User.build()).then(user => res.json(user));
});
ES.next version (2016):
you can use async functions that throw using this wrapper function copied from the official strongloop website:
let wrap = fn => (...args) => fn(...args).catch(args[2]);
then make the function in your router/controller like that:
router.post('/fn/email', wrap(async function(req, res) { ...throw new Error(); }
and finally have a normal catch all errors middleware:
app.use(function(err, req, res, next) { console.log(err); }
Obviously for this to work you need the babel transpiler currently
I use ExpressJS for routing, and bluebird for Promises.
I have the following code repeating for few routes, and in the end they all have the same .catch function, with responds with a json of failure.
router.get('/', function(req, res) {
return somePromise
.then(function doesSomething(someVariable) {
doSomething;
})
.catch(function catchesError(err) {
return res.json({ success: false });
});
});
I would like to extract the catchesError function, but then it wouldn't be able to use the res object.
Any suggestions?
Just create a function and pass the res object as an argument and return a function.
function makeErrorCatcher(res) {
return function catchesError(err) {
return res.json({
success: false
});
}
}
router.get('/', function (req, res) {
return somePromise
.then(function doesSomething(someVariable) {
doSomething;
})
.catch(makeErrorCatcher(res));
});
You can decorate .get to pass to have a default catch handler. (Assuming you're not interested in a custom router):
Object.keys(router).forEach(function(key){ // for each method
router[key+"P"] = function(path, fn){ // create a decorated alt
router[key].call(router, function(req, res, next){ // delegate
var that = this, args = arguments;
return Promise.try(function(){ // wrap to make throw safe
return fn.apply(that, args); // delegation
}).catch(function catchesError(err){
// LOG YOUR ERRORS, DON'T CATCH ALL
return res.json({ success: false });
});
});
};
});
This would let you do:
router.getP('/', function(req, res) {
return somePromise.then(function doesSomething(someVariable) {
doSomething;
});
});
Which will now catch errors automatically and send the appropriate JSON. Eliminating duplication or the possibility to forget errors altogether.
Pretty much like Ben Fortune's solution, using bind():
function catchesError(res, err) {
return res.json({ success: false });
}
router.get('/', function(req, res) {
return somePromise
.then(function doesSomething(someVariable) {
doSomething;
})
.catch(catchesError.bind(null, res));
});
Replace null with this if you're in a class.
If you're not planning in using promise chainging, you can add a catcher function that attaches the catch handler:
function catcher(promise, req, res) {
promise.catch(function catchesError(err) {
return res.json({ success: false });
});
return promise;
}
router.get('/', function(req, res) {
return catcher(somePromise, req, res)
.then(function doesSomething(someVariable) {
doSomething;
});
});
However if you want to benefit the awesome promise chaining mechanism, then you need to manually call the catch handler to make sure it's the last in the chain:
function makeCatchHandler(res) {
return function(err) {
return res.json({ success: false });
};
}
router.get('/', function(req, res) {
return catcher(somePromise, req, res)
.then(function doesSomething(someVariable) {
doSomething;
}).catch(makeCatchHandler(res));
});