Node express bad practice? - node.js

Good afternoon,
I'm trying to standardize my try {} catch() {} block across all my routes.
I created a Controller class as follows:
const { handleRouteError } = require('../handleRouteError');
class Controller {
async tryFunction(promise, onSuccess, onFail) {
try {
const data = await promise;
onSuccess(data);
} catch (error) {
handleRouteError(error);
onFail(error);
}
}
}
module.exports = Controller;
And I'm using it like this in my router:
const { getDays } = require('./controllers/getDays');
const controller = new Controller;
router.get('/days', async function getDayDays(req, res, next) {
await controller.tryFunction(
getDays(res.locals.user_id),
(data) => res.json(data),
(err) => next(err)
);
});
My questions:
Is it bad practice to pass the onSuccess and onFail function as I have done? All opinions welcome!
Will it eat up a lot of memory on the server?

I would try to stick to the built in error handling rather than inventing my own.
If your intention is to call handleRouterError with the error before passing the error to the caller you could do something like this:
class Controller {
async resolve(promise) {
try {
return await promise;
} catch (error) {
handleRouteError(error);
throw error;
}
}
}
And consume the resolve method like this:
const { getDays } = require('./controllers/getDays');
const controller = new Controller;
router.get('/days', async function getDayDays(req, res, next) {
try {
res.json(await controller.resolve(getDays(res.locals.user_id)));
} catch (err) {
next(err)
}
});

Related

Express - Wrapping controller functions with try-catch

I have an express backend application. The problem I have is that all the routes contains the same try-catch piece which causes code bloat in my program:
// routes.js
router.get('/', async (req, res, next) => {
try {
const data = extractData(req)
await foo(data)
} catch (err) {
next(err)
}
})
// controllers.js
async function foo(data) {...do smh}
As you see above, try { extractData() } catch (err) { next(err) } portion of the code exists in all of the routes defined in the app.
I tried to create a wrapper function that takes controller function as parameter and use it as:
// routes.js
router.get('/', controllerWrapper(req, res, next, foo))
// controller-wrapper.js
async function controllerWrapper(req, res, next, controllerFunc) {
try {
const data = extractData(req)
await controllerFunc(data)
} catch (err) {
next(err)
}
}
But this does not work due to function being invoked, and not being actually a callback.
How can I achieve this?
You should use a closure for this, so you can return the middleware function from controllerWrapper and use the controllerFunc inside the returned middleware
function controllerWrapper(controllerFunc) {
return async function (req, res, next) {
try {
const data = extractData(req)
await controllerFunc(data)
} catch (err) {
next(err)
}
}
}
router.get('/', controllerWrapper(foo))

How to execute response after asynchrous call made using Node.js

I need to send response after executing one asynchronous call using Node.js and MongoDB. I am explaining my code below.
module.exports.getDashboardDetail = async(req, res, next) =>{
console.log('Inside dashboard controller');
var customerVisited=await Allocation.collection.aggregate([
{$match:{}},
{$unwind:"$zone_list"},
{$unwind:"$zone_list.state_list"},
{$unwind:"$zone_list.state_list.location_list"},
{$unwind:"$zone_list.state_list.location_list.task_list"},
{$unwind:"$zone_list.state_list.location_list.task_list.loan_accounts_assigned"},
{$unwind:"$zone_list.state_list.location_list.task_list.loan_accounts_assigned.feedback_detail"},
{$group:{
_id:"total_feedback",
count:{$sum:1}
}
}
])
.toArray((err,docs)=>{
if (!err) {
customerVisited=docs
console.log('custm',customerVisited);
}else{
console.log('err',err);
}
})
var fosdata=await User.collection.countDocuments({},function(err,docs){
if (!err) {
fosdata=docs;
//res.send(data);
//console.log('nos of users',docs);
}
})
var data = {"no_of_visited_customer": customerVisited,"no_of_fos": fosdata,"no_of_alerts": 15,"status":'success'};
res.status(200).send(data);
//return res.status(200).json({ status: true, data : _.pick(data )});
}
Here I need to send the response after the aggregation method execution. Here Before coming the db result the response is sending.
You can write something like this,
var customerVisited=await Allocation.collection.aggregate([
{$match:{}},
{$unwind:"$zone_list"},
{$unwind:"$zone_list.state_list"},
{$unwind:"$zone_list.state_list.location_list"},
{$unwind:"$zone_list.state_list.location_list.task_list"},
{$unwind:"$zone_list.state_list.location_list.task_list.loan_accounts_assigned"},
{$unwind:"$zone_list.state_list.location_list.task_list.loan_accounts_assigned.feedback_detail"},
{$group:{
_id:"total_feedback",
count:{$sum:1}
}
}
])
.toArray((err,docs)=>{
if (!err) {
customerVisited=docs
console.log('custm',customerVisited);
}else{
console.log('err',err);
}
})
var data = {"no_of_visited_customer": customerVisited};
res.status(200).send(data);
But make sure the function in which this code is written is async (just add the word async before the word function), for example, if your function is,
function test() {
// some code
}
It should be
async function test() {
// some code
}
Why using toArray(err, docs) ?
// use your paths to models
const Allocation = require('../models/Allocation')
const User = require('../models/User')
module.exports.getDashboardDetail = async (req, res, next) => {
try {
console.log('Inside dashboard controller');
var customerVisited = await Allocation.collection.aggregate([
{$match:{}},
{$unwind:"$zone_list"},
{$unwind:"$zone_list.state_list"},
{$unwind:"$zone_list.state_list.location_list"},
{$unwind:"$zone_list.state_list.location_list.task_list"},
{$unwind:"$zone_list.state_list.location_list.task_list.loan_accounts_assigned"},
{$unwind:"$zone_list.state_list.location_list.task_list.loan_accounts_assigned.feedback_detail"},
{$group: {
_id:"total_feedback",
count:{$sum:1}
}
}
])
.toArray()
var fosdata = await User.collection.countDocuments({})
var data = {
"no_of_visited_customer": customerVisited,
"no_of_fos": fosdata,
"no_of_alerts": 15,
"status":'success'}
res.status(200).send(data);
} catch (e) {
// if it is just a controller we can set status and send response
// res.status(400).send(`Error ${e}`)
// if using express middleware pass error to express with next(e)
next(e)
}
}

Using async await properly in node js

To overcome callback hell in javascript, I'm trying to use async await from legacy code written in SQLServer procedure.
But I'm not sure my code might be write properly.
My first confusing point is when async function returns, should it return resolve() as boolean, or just return reject and handle with try-catch?
Here is my code snippets.
Please correct me to right direction.
apiRoutes.js
app.route('/api/dansok/cancelDansok')
.post(dansokCancelHandler.cancelDansok);
dansokCancelController.js
const sequelize = models.Sequelize;
const jwt = require('jsonwebtoken');
async function jwtAccessAuthCheck(accessToken) {
if (!accessToken) {
return Promise.reject('Empty access token');
}
jwt.verify(accessToken,"dipa",function(err){
if(err) {
return Promise.reject('TokenExpiredError.');
} else {
return Promise.resolve();
}
});
}
async function checkFeeHist(dansokSeqNo) {
let feeHist = await models.FeeHist.findOne({
where: { DansokSeqNo: dansokSeqNo}
});
return !!feeHist;
}
async function getNextDansokHistSerialNo(dansokSeqNo) {
....
}
async function getDansokFee(dansokSeqNo) {
....
}
async function doCancel(dansokSeqNo) {
try {
if (await !checkFeeHist(dansokSeqNo)) {
log.error("doCancel() invalid dansokSeqNo for cancel, ", dansokSeqNo);
return;
}
let nextDansokSerialNo = await getNextDansokHistSerialNo(dansokSeqNo);
await insertNewDansokHist(dansokSeqNo, nextDansokSerialNo);
await updateDansokHist(dansokSeqNo);
await updateVBankList(dansokSeqNo, danokFee.VBankSeqNo);
await getVBankList(dansokSeqNo);
} catch (e) {
log.error("doCancel() exception:", e);
}
}
exports.cancelDansok = function (req, res) {
res.setHeader("Content-Type", "application/json; charset=utf-8");
const dansokSeqNo = req.body.DANSOKSEQNO;
const discKindCode = req.body.HISTKIND;
const worker = req.body.PROCWORKER;
const workerIp = req.body.CREATEIP;
const accessToken = req.headers.accesstoken;
//check input parameter
if (!dansokSeqNo || !discKindCode || !worker || !workerIp) {
let e = {status:400, message:'params are empty.'};
return res.status(e.status).json(e);
}
try {
jwtAccessAuthCheck(accessToken)
.then(() => {
log.info("jwt success");
doCancel(dansokSeqNo).then(() => {
log.info("cancelDansok() finish");
res.status(200).json({ message: 'cancelDansok success.' });
});
});
} catch(e) {
return res.status(e.status).json(e);
}
};
You'll need to rewrite jwtAccessAuthCheck(accessToken) so that it keeps track of the outcome of its nested tasks. In the code you've written:
// Code that needs fixes!
async function jwtAccessAuthCheck(accessToken) {
// This part is fine. We are in the main async flow.
if (!accessToken) {
return Promise.reject('Empty access token');
}
// This needs to be rewritten, as the async function itself doesn't know anything about
// the outcome of `jwt.verify`...
jwt.verify(accessToken,"dipa",function(err){
if(err) {
// This is wrapped in a `function(err)` callback, so the return value is irrelevant
// to the async function itself
return Promise.reject('TokenExpiredError.');
} else {
// Same problem here.
return Promise.resolve();
}
});
// Since the main async scope didn't handle anything related to `jwt.verify`, the content
// below will print even before `jwt.verify()` completes! And the async call will be
// considered complete right away.
console.log('Completed before jwt.verify() outcome');
}
A better rewrite would be:
// Fixed code. The outcome of `jwt.verify` is explicitly delegated back to a new Promise's
// `resolve` and `reject` handlers, Promise which we await for.
async function jwtAccessAuthCheck(accessToken) {
await new Promise((resolve, reject) => {
if (!accessToken) {
reject('Empty access token');
return;
}
jwt.verify(accessToken,"dipa",function(err){
if(err) {
reject('TokenExpiredError.');
} else {
resolve();
}
});
});
// We won't consider this async call done until the Promise above completes.
console.log('Completed');
}
An alternate signature that would also work in this specific use case:
// Also works this way without the `async` type:
function jwtAccessAuthCheck(accessToken) {
return new Promise((resolve, reject) => {
...
});
}
Regarding your cancelDansok(req, res) middleware, since jwtAccessAuthCheck is guaranteed to return a Promise (you made it an async function), you'll also need to handle its returned Promise directly. No try / catch can handle the outcome of this asynchronous task.
exports.cancelDansok = function (req, res) {
...
jwtAccessAuthCheck(accessToken)
.then(() => {
log.info("jwt success");
return doCancel(dansokSeqNo);
})
.then(() => {
log.info("cancelDansok() finish");
res.status(200).json({ message: 'cancelDansok success.' });
})
.catch(e => {
res.status(e.status).json(e);
});
};
I strongly suggest reading a few Promise-related articles to get the hang of it. They're very handy and powerful, but also bring a little pain when mixed with other JS patterns (async callbacks, try / catch...).
https://www.promisejs.org/
Node.js util.promisify

Proper error handling using async/await in Node.js

I have the following code in my Node.js express app:
router.route('/user')
.post(async function(req, res) {
if(req.body.password === req.body.passwordConfirm) {
try {
var response = await userManager.addUser(req.body);
res.status(201).send();
} catch(err) {
logger.error('POST /user failed with error: '+err);
res.status(500).send({err:"something went wrong.."});
}
} else {
res.status(400).send({err:'passwords do not match'});
}
})
and userManager:
var userManager = function() {
this.addUser = async function(userobject) {
userobject.password_hash = await genHash(userobject.password_hash);
var user = new User(userobject);
return await user.save();
};
};
module.exports = userManager;
My question is: Will the try catch block in the route catch all errors thrown in addUser or will it only catch the ones that are thrown by user.save(), since that is the one that gets returned?
The answer is yes, it will catch all the errors inside try block and in all internal function calls.
async/await is just syntax sugar for promises. Thus if something is possible using promises then it is also possible using async/await.
For example both of the following code snippets are equivalent:
Using promises:
function bar() {
return Promise.reject(new Error('Uh oh!'));
}
function foo() {
return bar();
}
function main() {
return foo().catch(e => {
console.error(`Something went wrong: ${e.message}`);
});
}
main();
Using async/await:
async function bar() {
throw new Error('Uh oh!');
}
async function foo() {
await bar();
}
async function main() {
try {
await foo();
}
catch(e) {
console.error(`Something went wrong: ${e.message}`);
}
}
main();
In fact your code will not work since you don't use await on userManager.addUser.
It also forces you to use async on the parent function and that may break things up. Check express documentation (or just try if it works).
router.route('/user')
.post(async function(req, res) {
if(req.body.password === req.body.passwordConfirm) {
try {
var response = await userManager.addUser(req.body);
res.status(201).send();
} catch(err) {
logger.error('POST /user failed with error: '+err);
res.status(500).send({err:"something went wrong.."});
}
} else {
res.status(400).send({err:'passwords do not match'});
}
})

Nodejs promises catch "hell" then using sequelize orm

Currently I developing web app for nodejs using popular sequelize orm and typesciprt. Here is a example from my code
this.createNewGame(player.idPlayer).then((game) => {
this.getBestScore(player.idPlayer).then((bestScore) => {
defer.resolve({
idGame: game.idGame,
bestScore: bestScore
});
}).catch((error) => { defer.reject(error); });
}).catch((error) => { defer.reject(error); });
Here is one of the method
private getBestScore(idPlayer: number): Q.Promise<number> {
var defer = this.q.defer<number>();
GameModel.max<number>('score', { where: { 'idPlayer': idPlayer } }).then((result) => {
defer.resolve(result);
}).catch((error) => { defer.reject(error); });
return defer.promise;
}
I use catch in every method implementation and also in every call to method. I would like to have only one catch block in my expressjs router. I tried code like this and it works just fine, here is example:
//code in GameService class ...
getData(): Q.Promise<number> {
var defer = this.q.defer<number>();
this.method1().then((result) => {
defer.resolve(result);
});
return defer.promise;
}
private method1(): Q.Promise<number> {
var defer = this.q.defer<number>();
throw 'some error occurs here';
return defer.promise;
}
//router call GameService
router.get('/error-test', (req: express.Request, res: express.Response) => {
gameService.getData().then((result) => {
res.json(result);
}).catch((error) => { res.send(error); });
//works fine, here I get my thrown error
});
But in my previous example I need to use catch blocks everywhere otherwise If I get Unhandled rejection SequelizeDatabaseError or any other Unhandled rejection, nodejs stops working. Why I can't use only one catch block in my expressjs router when using calls to db with sequalize, like in my first example?
Sequelize operations return promises, so there is no reason to put Q into the mix. This is equivalent
private getBestScore(idPlayer: number): Q.Promise<number> {
return GameModel.max<number>('score', { where: { 'idPlayer': idPlayer } });
}
It doesn't return a Q.Promise (sequelize uses bluebird), but the implementations should be interoperable, since they are both 'thenables'.

Resources