Promise-chain vs. simplicity - node.js

I have a Node 8 / Express 4 / Mongoose 4 API and would like to generalize some code so that I can reuse it for other parts.
Consider the following code that would create a new user:
function postUser(req, res, next) {
var body = req.body;
if ("data" in body) {
var user = new User(body.data);
user.save(function(err, savedUser) {
if (err) {
if (err.name === 'MongoError' && err.code === 11000) {
// user already exists
res.status(400).json({status: "fail", message: "User already exists"});
} else {
return next(err);
}
} else {
// user successfully saved
res.json({status: "success", data: savedUser});
}
});
} else {
// malformed body
res.status(400).json({status: "fail", message: "Malformed body"});
}
}
Let's assume that I have other functions that would do similar work and some of them are callback-hell. How would I best generalize the above code? I thought about using promise-chains like this:
function postUser(req, res, next) {
validateBody(req.body)
.then(createNewUser)
.then(user => sendUser(user, res))
.catch(e => handleErrors(e, res));
}
function validateBody(body) {
return new Promise(function(resolve, reject) {
if ("data" in body) {
resolve(body.data);
} else {
reject(new InvalidBodyError());
}
});
}
function createNewUser(userObj) {
return new Promise(function(resolve, reject) {
var user = new User(userObj);
user.save(function(err, savedUser) {
if (err) {
if (err.name === 'MongoError' && err.code === 11000) {
// user already exists
reject(new UserAlreadyExistsError(userObj));
} else {
// other error
reject(err);
}
} else {
// user successfully saved
resolve(savedUser);
}
});
});
}
function handleErrors(e, res) {
if (e instanceof InvalidObjectIdError) handleInvalidObjectIdError(e, res)
else if (e instanceof UserNotFoundError) handleUserNotFoundError(e, res)
else if (e instanceof InvalidBodyError) handleInvalidBodyError(e, res)
else if (e instanceof UserAlreadyExistsError) handleUserAlreadyExistsError(e, res)
// TODO: handle unknown errors
}
As you can see, it looks cleaner and more reusable. But how will it perform under load? I am especially concerned about creating multiple promises per request. Does this scale or not?
Another way of solving it would be to create a generic base class that would solve the generic stuff and then extend this class with implementation-specific methods (pseudocode):
class Action {
constructor() {}
postDoc(Base, req, res, next) {
var body = req.body;
if ("data" in body) {
var doc= new Base(body.data);
doc.save(function(err, savedDoc) {
if (err) {
if (err.name === 'MongoError' && err.code === 11000) {
// docalready exists
res.status(400).json({status: "fail", message: "Doc already exists"});
} else {
return next(err);
}
} else {
// user successfully saved
res.json({status: "success", data: savedDoc});
}
});
} else {
// malformed body
res.status(400).json({status: "fail", message: "Malformed body"});
}
}
}
class UserAction extends Action {
constructor() {
super();
}
postUser(body, req, res, next) {
this.postDoc(User, req, res, next);
}
}
class AnotherAction extends Action {
constructor() {
super();
}
postAnother(body, req, res, next) {
this.postDoc(AnotherBase, req, res, next);
}
}
And then just use UserAction or AnotherAction (User is a mongoose model in my case).
Which one do you prefer?

I thought about using promise-chains like this, which is cleaner and more reusable. But how will it perform under load?
Just fine.
I am especially concerned about creating multiple promises per request. Does this scale or not?
Yes. Promises are cheap. Look at how many other objects and callback closures you are creating per request - it scales exactly the same.
However, you can further simplify:
function validateBody(body) {
return "data" in body
? Promise.resolve(body.data)
: Promise.reject(new InvalidBodyError());
}
function createNewUser(userObj) {
return new Promise(function(resolve, reject) {
new User(userObj).save(function(err, savedUser) {
if (err) reject(err);
else resolve(savedUser);
});
}).catch((err) => {
if (err.name === 'MongoError' && err.code === 11000) {
// user already exists
throw new UserAlreadyExistsError(userObj);
} else {
// other error
throw err;
});
});
}
Another way of solving it would be to create a generic base class that would solve the generic stuff and then extend this class with implementation-specific methods
No, don't do that. Inheritance is the wrong tool here. Creating generic helper functions like postDoc, which abstracts over the type of the document to create, are a good idea, but there's no good reason to put them in classes. Combine them with promises. If the number of parameters to the helper function gets out of hand, you can use objects, and even a class, but don't use inheritance - use different instances instead. For example, the code could look like this:
const userAction = new Action(User, UserAlreadyExistsError);
const anotherAction = new Action(AnotherBase, …);
function post(action) {
return (req, res, next) => {
action.postDoc(req)
.then(doc => send(doc, res))
.catch(e => handleErrors(e, res));
};
}

Related

Node: simulating network failure - res.status(404) not working

class gameInfo {
static async gameeee(req, res) {
try {
console.log(req.body);
await db.adb
.collection("game")
.findOne({ req.body.gameID}, async (err, result) => {
console.log("a");
if (err) {
console.log("b");
res.status(400);
} else if (result === null) {
console.log("c"); <------- this is called
res.status(404); <------ not happening
} else if (result !== null) {
res.json({ result });
}
});
} catch (err) {
console.log(err);
res.status(400);
}
}
}
console result is
a
c
I am trying to simulate the response failure due to no data. However, res.status(404) is not working. How can I send the error?
Also, I am super confused with among res.send, res.status and res.sendStatus. What are the differences using these three?
You still need to send or end the response, see https://expressjs.com/en/api.html#res.status:
res.status(404).end();
And yes, as the documentation says, you could just use sendStatus instead.
res.sendStatus(404) // equivalent to res.status(404).send('Not Found')

Nodejs WebService database Select

I am trying to do Nodejs database operations through the class, the route and method I pass through the route reaches the model and the select operation is done.
but when I send the data it is "undefined" in app.use
Route Code
var Select = require('../models/queryClass');
obj = new Select();
router.use(function (req, res) {
res.json(obj.Data(req.method, req.path))
});
Model Code
class Select {
Data(metodh, path) {
if (metodh == "GET" && path == "/data") {
db.query("select * from data ", function (err, result) {
if (err) {
return err;
}
else {
return result
}
})
}
}
};
The problem is that inside anonymous function the response is comming asynchronously and you never actually return the value outside that function.
Try to use promises, which should look something like this:
class Select {
Data(metodh, path) {
if (metodh == "GET" && path == "/data") {
return new Promise((resolve, reject) => {
db.query("select * from data ", function (err, result) {
if (err) {
reject(err);
}
else {
resolve(result);
}
})
});
}
}
};
And to get the answer you have to use the promise way; (then - catch)
obj.Data(req.method, req.path)
.then(result => console.log(result))
.catch(err => console.log(err))
Note: I didn't test it, but this way it should work. Before you never actually returned the result since it was async.

nodejs callback Error: Can't set headers after they are sent

i'm new!
I can't cope with the problem!
Did I try adding 'return' or not?
thx!
'GET /edit': async (ctx, next) => {
cache.get('user', function (err, result) {
if (err) {
console.log(err);
return;
}
var user = JSON.parse(result);
if (user == null) {
ctx.render('login.html', {
title: '登录'
});
} else {
ctx.render('edit.html');
}
})
// ctx.redirect('/login');
}

Update from bd with success but returns undefined on Controller Node.Js

Hy everyone, I'm having some troubles with my rest api. I have in my ui a button where I click to update the state of a bus ( visible / not visible). By clicking on the button I can update the state of the item on the map.
So my problem is when I update the info in my DB in my controller i get the result of this as undefined but the resolve of the db returns
{"command":"UPDATE","rowCount":1,"oid":null,"rows":[],"fields":[],"_parsers":[],"RowCtor":null,"rowAsArray":false}
My return.rows[0] becomes undefined on resolve (I console.log this value and i dont understand why this is happening).
ServicesController.js
ServicesController.prototype.updateMap = function (req, res, next) {
var data = req.body;
if (isEmptyObject(data)) {
res.status(400).send({error: errorMessage.emptyBody});
return;
}
if (data.sn === undefined || data.sn === "") {
res.status(400).send({error: "Invalid serial number"});
return;
}
Database.Services.getDeviceBySn(data.sn).then(function (device) {
var map_data={
"isRoot": data.root,
"visible": data.visible
}
Database.Services.addMapInfo(map_data, device.id).then(function (map) {
console.log("updateMap depois do addMapInfo --- map >>> ", map);
if (map) {
res.status(200).send(map);
} else {
res.status(404).end();
}
}).catch(function (e) {
res.status(500).send(e);
});
}).catch(function (e) {
res.status(500).send(e);
});
}
ServicesDatabase.js
ServicesDatabase.prototype.addMapInfo = function (data, deviceId) {
return new Promise(function (resolve, reject) {
pg.connect(dbStatsConnectionString, function (err, client, done) {
if (err) {
reject(err);
return
}
client.query("UPDATE device_services SET data=jsonb_set(data::jsonb,'{map}',$1::jsonb,true), modified_date=NOW() WHERE device_id=$2", [data, deviceId], function (err, result) {
done();
if (err) {
reject(err);
} else {
resolve(result.rows[0]);
}
});
});
});
}
My parameters are data {"isRoot":"false","visible":"online"} and deviceId "1f110136-9490-4ea5-a46d-3fdfa65ea0ab"
My controller always return 404 because of this
if (map) {
res.status(200).send(map);
} else {
res.status(404).end();
}
Anyone can help me? I dont get it...

Populating navigation bar data from DB

I have a sails application and I want to populate the navigation bar drop-down menu data from DB.
I am using policies to call service and populate sails.config.views.locals.HeaderData variable
But because of some async feature of sails the service is getting called only when the controller has sent the response and not when the request came to the policy.which is giving me empty array in the ejs
Policy
module.exports = function (req, res, next) {
try {
if (sails.config.views.locals.HeaderData == 'undefined' || sails.config.views.locals.HeaderData == 'is not defined' || sails.config.views.locals.HeaderData == undefined) {
sails.config.views.locals.HeaderData = [];
}
if (_.isEmpty(sails.config.views.locals.HeaderData)) {
MenuItems.getMenuItems('items', function (err, response) {
if (err) {
sails.log.debug(err);
}
else {
sails.config.views.locals.HeaderData = response[0].dataValues;
}
})
}
}
catch (e) {
console.log("errrrrr", e);
}
next();
}
next() is called before MenuItems.getMenuItems might have finished. So move the next() statement inside the callback:
MenuItems.getMenuItems('items', function (err, response) {
if (err) {
sails.log.debug(err);
}else{
sails.config.views.locals.HeaderData = response[0].dataValues;
}
next();
})

Resources