I am trying to improve my code in node.js / sail.js and I am fighting server response in promisses.
When you look at the first .then function you can see that method returns false in case of forbidden access or notFound. Then, in the next .then functions I must check if the return type is === false to skip to section and avoid sending http headers twice. Can this be improved somehow, to skip all next .then methods in case of failure? I can throw an Exception to go in the last .catch but then there must be a case to switch between all possible states. (i.e. forbidden, serverError or even not found)
Notification.findOne({id: req.param('id')})
.then(function(notification) {
if (!notification) {
res.notFound();
return false;
}
if (notification.triggeredBy != req.session.user.id) {
res.forbidden();
return false;
}
return notification;
})
.then(function(notification) {
if (notification === false) {
return false;
}
return Notification.update(notification.id, actionUtil.parseValues(req));
})
.then(function(notification) {
if (notification === false) {
return false;
}
res.json(notification);
})
.catch(function(err) {
sails.log(err);
res.serverError({message: 'A server error occurred.'});
})
If I would do this, first I seperate logic and receving/sending function. Second I specify listing of error codes. And it will be like that:
NotificationService.js
/*
Listing of error codes: {
* [1] Object not found
* [2] Forbidden
* [3] Server error
}
*/
module.exports = {
nameOfMethod: function(ID, sessionID) {
return new Promise(function(resolve, reject) {
Notification.findOne({ id: ID })
.then(function(notification) {
if (!notification) return reject({ error_code: 1 });
if (notification.triggeredBy !== sessionID) return reject({ error_code: 2 });
Notification.update(notification.id, actionUtil.parseValues(req))
.then(function(notification) {
return resolve(notification); // finally return our notification
})
.catch(function(err) {
sails.log.error(err); // It's good when log is classified. In this case is error
return reject({ message: 'A server error occurred.' });
});
})
.catch(function(err) {
sails.log.error(err);
return reject({ message: 'A server error occurred.' });
});
});
}
};
NotificationController.js
module.exports = {
notifyMe: function(req, res) {
const ID = req.param('id'), sessionID = req.session.user.id;
NotificationService.nameOfMethod(ID, sessionID)
.then(function(notification) {
return res.send(notification);
})
.catch(function(err) {
switch (err.error_code) {
case 1:
return res.notFound(err);
case 2:
return res.forbidden(err);
default:
return res.serverError(err);
}
});
}
};
In case where I use switch I think it is better way to select right response but on this time I haven't any idea
See how filtered .catch() is implemented in Bluebird - it can be useful in your case to throw all errors you need but avoid having a big switch/case block in the catch handler:
.catch(
class ErrorClass|function(any error)|Object predicate...,
function(any error) handler
) -> Promise
.caught(
class ErrorClass|function(any error)|Object predicate...,
function(any error) handler
) -> Promise
This is an extension to .catch to work more like catch-clauses in
languages like Java or C#. Instead of manually checking instanceof or
.name === "SomeError", you may specify a number of error constructors
which are eligible for this catch handler. The catch handler that is
first met that has eligible constructors specified, is the one that
will be called.
Example:
somePromise.then(function() {
return a.b.c.d();
}).catch(TypeError, function(e) {
//If it is a TypeError, will end up here because
//it is a type error to reference property of undefined
}).catch(ReferenceError, function(e) {
//Will end up here if a was never declared at all
}).catch(function(e) {
//Generic catch-the rest, error wasn't TypeError nor
//ReferenceError
});
See: http://bluebirdjs.com/docs/api/catch.html#filtered-catch
Instead of:
return false;
you can use:
return Promise.reject(someReason);
or:
throw someReason;
and you won't have to check for those false values - just use (possibly multiple) catch handlers.
Related
I have typescript class with some functions in it. Each function has a try catch block where upon hitting the catch it returns a pre defined response.
I am writing unit tests using mocha and chai and I am having trouble trying to explicitly hit the catch blocks.
For example, consider this simple function below
public async verifyCode(email: string, code: string) {
try {
let result: CodeInterface | null = //call db to get matching code
if(result === null) {
return {status: 401, message: INCORRECT_FELLOWSHIP_CODE_MESSAGE};
}
return result._id;
} catch (error) {
Logger.log(LoggerLogTypes.ERROR, {
class_name: "LaunchServiceImpl",
function_name: "verifyFellowshipCode",
message: error.message,
stack: error.stack,
additional_info: {
code
}
});
return false;
}
}
I want to write a test case where I can just send the control directly to the catch block to get the false value. This is a very simplistic example but in few other functions I am doing a lot more in the catch block.
My mocha unit test looks like this:
it("should go to the catch block and return false in case of exception", async function() {
let serviceInstance = new MyClass();
let response = await serviceInstance.verifyCode("john#test.com", "abc123");
// how do I directly jump to the catch block here??
expect(response).to.equal(false);
});
Suppose you have a function that will throw an error with the message User not found so you can test like this:
profileServiceInstance.getProfile('1').catch((err) => {
expect(() => {
throw err;
}).to.throw(Error, 'User not found');
});
class AuthController {
static methods = {
GET: {
'/auth/signup': {
func: AuthService.signUp,
response: (data, res) => {
res.statusCode = 200;
res.end(JSON.stringify(data));
},
},
},
};
static use(req, res) {
const route = this.methods[req.method][req.url];
if (!route) {
res.statusCode = 404;
res.end(JSON.stringify({ message: 'Not found 404!' }));
return;
}
try {
const data = JSON.parse(req?.body?.data || '{}');
const result = route.func({ ...data });
route.response(result, res);
} catch (err) {
console.log(err, 'here');
res.statusCode = err.statusCode || 500;
res.end(JSON.stringify(err.message));
}
}
}
class AuthService {
static async signUp({ login, password }) {
if (!login || !password) throw new BaseError(400, 'kl', 'Custom error');
}
}
It shows the error in console but try catch block doesn't see it.
Here is the traceback.
I don't know what the reason is because the function which throws error is inside of the block. Help please!
The trace back that you attached tells you exactly what the problem is and what you need to do:
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
You can't catch an exception thrown by an async function with a try..catch block outside of that function, because script execution reaches the catch block before the async execution is finished. You therefor have to use .catch(..) instead:
const result = route.func({ ...data }).catch((err) => {
console.log("catched error: ", err);
});
I see one issue. You have declared signUp() to be async. That means it always returns a promise and it means that any throw operations inside it reject that promise that it returns (the exception doesn't propagate synchronously). But, when you attempt to call it here:
const result = route.func({ ...data });
You don't await it so when signUp() rejects, the promise goes into result, but nobody ever handles the fact that the promise rejected and you get UnhandlePromiseRejectionWarning from the system.
I can't see the whole overall design (of all the other routes), but perhaps you just need to add await to this:
const result = await route.func({ ...data });
And, you would have to make .use() be async also.
Or, if signUp() doesn't actually need to be async, then just remove the async from its declaration and the throw will be synchronous (instead of being turned into a rejected promise) and your try/catch will catch it then.
I am struggling with some code... The 2 examples below I would think would work the same but the second example throws an error? I am also struggling to figure out the error, it's not bubbling up? Admittedly I am not a seasoned node developer so any guidance would be much appreciated! If it's relevant the create method in the module is calling the sequelize create.
This works
var p1 = deliverabiltyConfigs.create2(cfgObject);
return Promise.all([p1]).then(function([res1]) {
res.json({result: res1})
});
This does not
deliverabiltyConfigs.create2(cfgObject).then(res1 =>{
res.json({result: res1})
})
Here is the function that I am calling in a controller module
exports.create2 = (dConfig) => {
DeliverabilityConfig.create(dConfig)
.then(data => {
return data
})
.catch(err => {
return {
message:
err.message || "Some error occurred while createing this config."
};
});
};
The create2 function always returns null, so neither invocation will work. Promise.all([p1]) hides the problem, returning a promise to perform an array of no promises.
create2(cfgObject).then(res1 =>{ attempts to invoke then() on null, generating a more obvious error. But neither way works.
Fix by deciding which promise syntax you want, using each as follows:
Using original promise syntax....
exports.create2 = dConfig => {
// note the return
return DeliverabilityConfig.create(dConfig)
.catch(err => {
const message = err.message || "Some error occurred while createing this config.";
return { message };
});
};
// caller
deliverabiltyConfigs.create2(cfgObject).then(result =>{
res.json(result);
})
With recent syntactic sugar...
exports.create2 = async (dConfig) => {
try {
// its fine to not await here, since the caller will await
// but just to illustrate how you might perform more async work here...
return await DeliverabilityConfig.create(dConfig);
} catch (err) {
const message = err.message || "Some error occurred while createing this config."
return { message }
}
}
// caller
var result = await deliverabiltyConfigs.create2(cfgObject);
res.json(result);
Use Promise.all() to run >1 promise concurrently. You've only got one promise in the OP, so no reason for it here.
I have looked at MongoDB documentation for deleteMany() but it seems the only error it throws is WriteConcernError.
I am using Insomnia to make my requests.
Here is my request:
DELETE HTTP://localhost:5000/api/users/delete/usernames?usernames=["a","b","c"]
As you can see I have an array in query string
so I pass that to my function
# user.controller.js
function _deleteByUsernames(req, res, next) {
userService.deleteByUsernames(JSON.parse(req.query.usernames))
.then(() => res.status(200).json({"message": "User(s) successfully deleted!"}))
.catch(err => next(err));
}
# user.service.js
async function _deleteByUsernames(usernames) {
try {
console.log(usernames);
await User.deleteMany({username: {$in: usernames}});
} catch (err) {
throw err;
}
}
I know there no documents with usernames a, b and c
but deleteMany() doesn't return any error something like "Coulnd't find given parameter" etc.
because I don't want to response with "User(s) successfully deleted".
How can I catch that error if there is one.
Or How should I handle that?
You may change your functions to below,
# user.controller.js:
put async/await in function, and add code in try/catch block, and pass res as param in service function deleteByUsernames,
async function _deleteByUsernames(req, res, next) {
try {
await userService.deleteByUsernames(res, JSON.parse(req.query.usernames));
} catch (err) {
next(err);
}
}
# user.service.js:
deleteMany(), This function calls the MongoDB driver's Collection#deleteMany() function. The returned promise resolves to an object that contains 3 properties:
ok: 1 if no errors occurred
deletedCount: the number of documents deleted
n: the number of documents deleted. Equal to deletedCount.
async function _deleteByUsernames(res, usernames) {
let response = await User.deleteMany({ username: { $in: usernames } });
// ERROR
if (response.ok != 1) {
res.status(400).json({ "message": "User(s) not deleted, Something want wrong!" });
}
// SUCCESS
else {
res.status(200).json({
"message": `${response.deletedCount} User(s) successfully deleted out of ${response.n}"
});
}
}
Code is not tested, you can workaround and see what happens!
I think there is no error for no found parameters.
I don't know this is better than nothing for now.
I am not going to mark this as answered because I don't think this is the answer
async function _deleteByUsernames(usernames) {
return await User.deleteMany({username: {$in: usernames}})
.then(result => {
console.log(result);
return (result.deletedCount === 0 ?
"None of the selected user(s) deleted!":
(result.deletedCount !== usernames.length ?
`${result.deletedCount} out of ${usernames.length} selected user(s) successfully deleted!`:
"All selected user(s) successfully deleted!"))
})
.catch(err => {
return `Delete failed with error: ${err}`;
})
}
You can save your delete result in a variable and check for the error
async function _deleteByUsernames(usernames) {
try {
console.log(usernames);
let userDeleteResult = await User.deleteMany({username: {$in: usernames}});
if(!userDeleteResult ){
res.json({status: false, error: 'Some error'}) // or pass the error object here
}
} catch (err) {
throw err;
}
}
For example,
Comments.findOne({user: req.user.id}).exce()
.then(function(comment) {
if(comment) {
// how to make this return immediately and break the rest then?
return res.json({error: 'Already commented'});
} else {
return Posts.findOne({postId: req.params.id}).exec();
}
})
.then(function(post) {
if(post) {
var comment = new Comment({user: req.user.id, data: req.body.comment})
return {post: post, comment: comment.save()};
} else {
return res.json({error: 'Post not exist'});
}
})
.then(function(data) {
post.comments.push(comment._id);
return post.save();
});
.then(function(post) {
return res.json({ok: 1})
})
.catch(function(e)) {
return res.json(error: e);
});
Is this promise written right?
How to write this kind of promise?
Callbacks/Promises is a headache...
You're using bluebird, it supports cancellation. Here's an example:
var Promise = require('bluebird');
// enable cancellation
Promise.config({
cancellation: true
});
// store your promise chain
var myPromise = Promise.resolve().then(() => {
return 'foo';
}).then((res) => {
console.log(res);
// call cancel on the chain when needed
myPromise.cancel();
return res;
}).then((res) => {
// this will not be executed
console.log(res + '2');
});
You just need to throw or return a reject promise to trigger the error handling in promises as show in this example:
Comments.findOne({user: req.user.id}).exce()
.then(function(comment) {
if(comment) {
// to bypass all the other .then() resolve handlers, either
// throw an error here or return a rejected promise
throw new Error(res.json({error: 'Already commented'}));
} else {
return Posts.findOne({postId: req.params.id}).exec();
}
})
Promises are "throw safe" which means that .then() will catch any exception thrown and turn it into a rejected promise. This will bypass any following .then() resolve handlers and will instead go to the next reject handler or .catch() handler.
FYI, you should be careful in your code because when you .catch() and then just return from that, it changes your promise state from rejected to resolved and it will then look like a successful promise when you actually had an error and any caller will think everything was successful. This is because the promise infrastructure assumes that if you .catch() an error and return a value that you have "handled" the error and the state is now resolved successfully. To allow the error to continue to propagate to higher callers from a .catch(), you have to re throw or return a rejected promise:
blah(...).then(...)
.catch(function(e)) {
throw new Error(res.json(error: e));
});