Preface:
req.helpers.consoleMessage
function seen bellow is a function with some simple logic in it that determines when to and not to display a certain level of detail based the existent of debug being turned on in the app configuration AND the non-null/undefined state of the objects being displayed. The entire messages gets stringified using JSON
and returned to be displayed on the console.Code:
Here is an example of some code showing these symptoms.
This is a delete
route for the:team
:comment
units in an API I'm working on. I have intentionally left the line var response = user.comments;
with an error in it referencing to a user
object when in fact it should be team
which should be returned by the calling function and passed into the Promise.then()
. This should cause a reference error as user is not defined.
var console = clim("(DELETE /api/v1/team/:team/comments/:comment):", logger);
// create a filters request for mongoose
var query = {};
// determine if the :team param is a username or an object id
req.helpers.validateObjectId(req.db, req.params.team) ? query._id = req.params.team : query.name = req.params.team;
if(req.helpers.validateObjectId(req.db, req.params.comment)) {
// looks good; create an update object
var update = { $pull: { comments: { _id: req.params.comment } } };
// find the comment using the query above and pull the comment id
req.models.Team.findOneAndUpdate(
query,
update,
{safe: true, new : true}
).then(function(team){
if(!team){
// create the response object
var response = {
success: false,
message: "Team not found"
};
// log request
console.info(req.helpers.consoleMessage(req, response, null));
// respond with an appropriate array
res.status(404).json(response);
}else{
// create the response object using the teams's comments
var response = user.comments;
// log request
console.info(req.helpers.consoleMessage(req, response, null));
// respond with the team comments array
res.status(200).json(response);
}
}).then(null, function(err){
// create the response
var response = { success: false, message: req.config.debug ? err: "An error has occur with your request; please try again" };
// log the errors
console.error(req.helpers.consoleMessage(req, response, err));
// or send a 500 internal server error
res.status(500).json(response);
});
}else{
// create the response
var response = { success: false, message: "Comment id is not a valid object id" };
// log the errors
console.info(req.helpers.consoleMessage(req, response, null));
// or send a 500 internal server error
res.status(500).json(response);
}
Symptom:
Calling this route will produce an error and cause the .catch()
to fire in an attempt to recover from the errored state however the err
object appears to be empty.
Ex. HTTP Response: { success: false, message: {} }
Ex. Console log (abridged for clarity) { req: {...}, res: {...}. err: {} }
Conclusion:
The err
object appears to be empty...
catch " around the executor automatically catches the error and turns it into rejected promise. This happens not only in the executor function, but in its handlers as well. If we throw inside a . then handler, that means a rejected promise, so the control jumps to the nearest error handler.
catch() The catch() method returns a Promise and deals with rejected cases only. It behaves the same as calling Promise.
A catch -block contains statements that specify what to do if an exception is thrown in the try -block. If any statement within the try -block (or in a function called from within the try -block) throws an exception, control is immediately shifted to the catch -block.
If you throw an error inside the promise, the catch() method will catch it, not the try/catch. In this example, if any error in the promise1, promise2, or promise4, the catch() method will handle it.
Problem:
Logging the error object to console by itself will reveal that the object is indeed not empty and grabbing properties such as err.message
is very doable.
The problem is that JS Error object cannot be naively stringified using JSON
. This - along with ways to deal with this problem - are described in detail in the associated SOF Question: Is it not possible to stringify an Error using JSON.stringify?.
Updated code:
The following code changes were made to specify the message be shown in the HTTP response and the helper function (previously described) has been updated to specific the details of the error to be shown: Ex: { err: { message: err.message, stack: err.stack } }
and so on.
var console = clim("(DELETE /api/v1/team/:team/comments/:comment):", logger);
// create a filters request for mongoose
var query = {};
// determine if the :team param is a username or an object id
req.helpers.validateObjectId(req.db, req.params.team) ? query._id = req.params.team : query.name = req.params.team;
if(req.helpers.validateObjectId(req.db, req.params.comment)) {
// looks good; create an update object
var update = { $pull: { comments: { _id: req.params.comment } } };
// find the comment using the query above and pull the comment id
req.models.Team.findOneAndUpdate(
query,
update,
{safe: true, new : true}
).then(function(team){
if(!team){
// create the response object
var response = {
success: false,
message: "Team not found"
};
// log request
console.info(req.helpers.consoleMessage(req, response, null));
// respond with an appropriate array
res.status(404).json(response);
}else{
// create the response object using the teams's comments
var response = team.comments;
// log request
console.info(req.helpers.consoleMessage(req, response, null));
// respond with the team comments array
res.status(200).json(response);
}
}).then(null, function(err){
// create the response
var response = { success: false, message: req.config.debug ? err.message : "An error has occur with your request; please try again" };
// log the errors
console.error(req.helpers.consoleMessage(req, response, err));
// or send a 500 internal server error
res.status(500).json(response);
});
}else{
// create the response
var response = { success: false, message: "Comment id is not a valid object id" };
// log the errors
console.info(req.helpers.consoleMessage(req, response, null));
// or send a 500 internal server error
res.status(500).json(response);
}
Why am I sharing such a simple concept?
I searched for hours trying to figure out what I was doing incorrectly with my promise chain structures and used the keywords empty
and error
(along with every combination of words regarding JS Promises) but none of my searches came up with anything useful other than confirming that I was approaching this correctly. Everything appeared to be fine with my helper script and I couldn't seem to do the right debugging steps to figure out where the problem was until I happened upon trying to output the err
object directly into the console (why would I need to do this? I already was... or so I thought).
So I guess you could say I'm trying to save some folks some time in case anyone runs into a similar situation and is thinking "why are my promises not working as intended!" and, like me, happen to be searching in the incorrect direction.
Happy Coding!
The problem with an empty object occurs when you trying to use JSON.stringify
const error = new Error('Oops');
const output = JSON.stringify(error);
console.log(output); // {}
const error = new Error('Oops');
const output = JSON.stringify(error.message); // <--- Use error.message to fix that
console.log(output); // "Oops"
For whoever wondering why the throw error()
is just an empty object, it is because you need to log it as error.message
accessing it message
property.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With