Background
Let's say that I'm working with NodeJS + Express. I have certain error handlers registered with Express that will take care of all errors that might e up in my application in the appropriate way(s).
As such, I throw errors in my application whenever I need to do so. If there is an unhandled error, I let it propagate until it reaches an error handler. However, while attempting to throw errors while inside of a promise chain, I ran into a problem. Take the following example:
function find() {
// consider this to be a promise from a library such as Bluebird
return new Promise(function (resolve, reject) {
// ... logic ...
});
}
function controller (req, res) {
// ... omitted ...
find().then(function (result)) {
if (result) {
// let 'res' be the Express response object
res.send("It exists!");
} else {
// let SpecificError be a prototypical subclass of Error
throw new SpecificError("Couldn't find it.");
}
}).catch(function (error) {
// throw the error again, so that the error handler can finish
// the job
throw error;
});
}
Whereas I have been expecting the error that I am re-throwing to eventually hit at least the generic error handler, I am instead seeing the requests that I send to my application hang, and the promise library that I am using plain of a Unhandled rejection
.
Question
Quite simply, I am wondering how to resolve the fact that I appear to be mishandling the rejection that I am creating by throwing an error in my promise chain.
Edit: for clarification as to what (specifically) the error handler and controller
function are, see the ments below.
Background
Let's say that I'm working with NodeJS + Express. I have certain error handlers registered with Express that will take care of all errors that might e up in my application in the appropriate way(s).
As such, I throw errors in my application whenever I need to do so. If there is an unhandled error, I let it propagate until it reaches an error handler. However, while attempting to throw errors while inside of a promise chain, I ran into a problem. Take the following example:
function find() {
// consider this to be a promise from a library such as Bluebird
return new Promise(function (resolve, reject) {
// ... logic ...
});
}
function controller (req, res) {
// ... omitted ...
find().then(function (result)) {
if (result) {
// let 'res' be the Express response object
res.send("It exists!");
} else {
// let SpecificError be a prototypical subclass of Error
throw new SpecificError("Couldn't find it.");
}
}).catch(function (error) {
// throw the error again, so that the error handler can finish
// the job
throw error;
});
}
Whereas I have been expecting the error that I am re-throwing to eventually hit at least the generic error handler, I am instead seeing the requests that I send to my application hang, and the promise library that I am using plain of a Unhandled rejection
.
Question
Quite simply, I am wondering how to resolve the fact that I appear to be mishandling the rejection that I am creating by throwing an error in my promise chain.
Edit: for clarification as to what (specifically) the error handler and controller
function are, see the ments below.
-
What is "the generic error handler" you are expecting it to hit? If
controller
is a route, you should be using the thirdnext
param and doingnext(error)
– loganfsmyth Commented Sep 25, 2015 at 22:51 -
I think you see what I'm trying to do --
next(error)
sounds like the right answer – nmagerko Commented Sep 26, 2015 at 2:11 -
If you show how you're calling
controller()
, we'd have a much better idea what you're doing and what options would make the most sense. – jfriend00 Commented Sep 26, 2015 at 3:04 -
The generic error handler would simply be something like
app.use(function(err, req, res, next) { ... })
ifapp
was an Express app. Thecontroller
function is the handler for a route, such as/
. – nmagerko Commented Sep 26, 2015 at 3:40 - Which generic error handler do you expect to get hit by the rethrown error? There is none - except the one that plains about the unhandled rejection. – Bergi Commented Sep 26, 2015 at 16:28
2 Answers
Reset to default 4Assuming you have bound your function with something like
app.get('/', controller);
When Express calls controller
, it has yielded 100% control to you. If an exception is thrown synchronously from controller
, Express is nice and will also treat that as an error for you. As soon as you invoke any asynchronous code however, it is your responsibility to decide how to handle any errors.
In the case of Express, you have two options:
- Since you were passed
req
andres
, you can catch an error and send whatever response you want back to the user. controller
actually has a function signature offunction(req, res, next)
in Express. This is a very mon format.
The next
callback expects to be called if you have not written anything in the response yet. If you call next()
with no arguments, this tells Express to look keep processing the set of URL handlers that it has, trying to find one that will handle the request, returning a 404 if none are found.
If however, you pass an argument to next
like next(err)
, Express will skip over remaining URL handlers, instead looking for error handlers. Express allows you to register custom handlers, but if none are found, it will return a 500.
So what should you do in your example? You probably want something like
function controller (req, res, next) {
find().then(function (result)) {
if (!result) throw new SpecificError("Couldn't find it.");
res.send("It exists!");
}).catch(next);
}
That means that if any exception is thrown inside of the promise chain, the next
function will be called with it, and Express will take over from there.
Promise handlers are "throw safe". That means any exception you throw in any promise handler will be caught automatically and turned into a rejected promise. That is how the specification is written for promises and how they work (except for some versions of jQuery promises, but that's just because they aren't following the spec).
So, if you are getting "Unhandled rejection" from your promise library, that's designed to be a helpful warning to tell you that you had a rejected promise that had no handler for it so the rejection was silently ignored which is usually a coding mistake.
And, in fact in your controller()
function, you have exactly that:
function controller (req, res) {
// ... omitted ...
find().then(function (result)) {
if (result) {
// let 'res' be the Express response object
res.send("It exists!");
} else {
// let SpecificError be a prototypical subclass of Error
throw new SpecificError("Couldn't find it.");
}
}).catch(function (error) {
// throw the error again, so that the error handler can finish
// the job
throw error;
});
}
If you get to the line where it says throw new SpecificError
, then that will turn the promise into a rejected promise. That will then cause your .catch()
handler to get called. In that handler, you throw again which will keep the promise as a rejected promise. So, the original promise that started with find().then(...)
will now be a rejected promise. But, there are no more reject handlers and you are not returning the promise from controller()
. So, you have an unhandled rejected promise at that point. That is usually a coding mistake.
You have several choices for how to correct this coding mistake:
You can handled the error yourself in the
.catch()
handler by calling some sort of error handling function that you pass the error to and you pass theres
argument to and then don't throw the error.You can return the promise from your
controller()
function and Whatever code is calling that function can then handle the rejected promise there.