I'm trying to avoid callback hell by breaking down my Express / Kraken.js controller into smaller callback functions.
I was processing a request and had about 6 levels of nested anonymous callback functions.
so now I have my main function that looks like this:
// ugh, I know this isn't right
var globalProducts = {};
module.exports = function (server) {
server.get('/project', function (req, res) {
var data = req.query;
globalData = data;
if(!data.projectId || !data.ruleSetId)
res.json({error: "Incomplete input data."});
// pass response to products call back
Project.findOne({ _id: mongoose.Types.ObjectId(data.projectId) }, setUpProducts);
});
};
function setUpProducts(err, project){
// get all products and pass them down the pipe
project.findAllChildren(setUpRules);
}
function setUpRules(err, products) {
// we need to access products in another function
globalProducts = products;
// find the rule set and build the rule Flow
RuleSet.findOne({ _id: mongoose.Types.ObjectId(globalData.ruleSetId) }, function(err, ruleSet) {
ruleSet.buildFlow(processFlow);
});
}
My question is what is the best way to pass around info between callbacks ? My solution was var globalProducts = {}; but to me, the controller contain any 'global state' .. whats the best way to handle this ?
Doing this is a bad idea. It will cause race condition type issue — basically it's the same as sharing data in multithreaded environment. Instead you can use req or res to store data. To do that you need them in scope, so you can define all functions inside route handler or make each function a middleware so it will have req and res as arguments. Here is an example of this approach:
function check (req, res, next) {
if(!req.query.projectId || !req.query.ruleSetId) return res.json({error: "Incomplete input data."});
next()
}
function findProject (req, res, next) {
Project.findOne({ _id: mongoose.Types.ObjectId(req.query.projectId) }, after);
function after (err, project) {
if (err) return next(err);
req.project = project;
next();
}
}
function findProducts (req, res, next) {
req.project.findAllChildren(after)
function after (err, products) {
if (err) return next(err);
req.products = products;
next();
}
}
function respond (req, res) {
res.render('view', {
products : req.products,
project : req.project
});
}
module.exports = function (server) {
server.get('/project', check, findProject, findProducts, respond);
};
Related
I want to keep routes separate from controller.
My route is:
'use strict';
module.exports = function(app) {
var controller = require('../controllers/controller');
app.route('/').get(controller.index);
};
And controller is:
exports.index = function() {
request = new Request(
"MYQUERY",
function(err, rowCount) {
if (err) {
console.log(err);
} else {
console.log(rowCount + ' rows');
}
connection.close();
}
);
request.on('row', function(columns) {
columns.forEach(function(column) {
if (column.value === null) {
console.log('NULL');
} else {
console.log(column.value);
}
});
});
connection.execSql(request);
};
I am able to see the result in the terminal console but I want to return it as JSON to http. I can use the following if I am using controller and routes all together:
router.get('/about', function (req, res) {
res.send('About this wiki');
})
The callback function to .get (or any router request handler) takes at least two arguments: request and response. You can see this with your example:
router.get('/about', function (req, res) {
res.send('About this wiki');
})
You could rewrite this to make the callback a named function rather than an anonymous function:
const aboutHandler = function (req, res) {
res.send('About this wiki');
});
router.get('/about', aboutHandler);
Your controller.index is the same kind of function, so it will take those two arguments. You just have to change your function to take them:
exports.index = function (req, res) {
This will give you access to res, and you can use it as you need to do send the response via res.send or res.json if you build a JSON object by accumulating the row results. You can use request.on('end' ... to know when the query has emitted all its results.
I might be misunderstanding your question, but do you mean res.json(...);?
exports.signin = function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err || !user) {
res.status(400).send(info);
} else {
// Remove sensitive data before user.password = undefined; user.salt = undefined;
req.login(user, function(err) {
if (err) {
res.status(400).send(err);
} else {
res.json(user);
}
});
}
})(req, res, next);
};
This a piece of code in the "MEAN Web Development" book by AmosQ.Haviv.Who could tell me what the method passport.authenticate()'tail:(req, res, next) means?Is that a Closure?
passport.authenticate() will probably take in the settings 'local' (a domain ? ) and the calllback to create a new function that will do the authentication.
This function will indeed create a closure over 'local' and the callback.
The new auth function will expect 3 parameters: the original req(uest), res(ponse) object and a next parameter and is immediately called using the ( req, res, next ) syntax.
So it's very likely that this specific function will do the login and then run the callback used to create the auth function, passing req and res back into the callback.
Look at it as a way to use the passport.authenticate() method to create different versions of logins you can use. One for 'local', one for 'otherDomain', etc.
I am using Express.js as http server. Defined all my routes.
Most endpoints need to verify session before returning a response. E.g. below code serves users in the system and list of services respectively:
function getUsers(req, res, next) {
verifyUser(req, res, next, function () {
//serve users
});
}
function getServices(req, res, next) {
verifyUser(req, res, next, function () {
//serve services
});
}
You probably noticed there is a verifyUser function which validates the session. Which is as below.
function verifyUser(req, res, next, callback) {
var sessionKey = req.cookies.sessionKey;
var user = users.userBySession(sessionKey);
if (user) {
callback(req, res, next, user);
} else {
res.status(401).send({
message: 'Unauthorized'
});
}
}
As you can see I keep passing in req, res and next parameters along with a callback whenever I use this function.
I tried to use apply function to make it easier. Changed my getUsers function like this:
function getUsers(req, res, next) {
verifyUser
.apply(null, arguments, function () {
//serve users
});
}
The problem with this approach is callback is not passed into verifyUser function. And I don't really like passing null as scope with each call.
How can I achieve this by writing less and better code ? Any ideas?
You could use bind to create a 'partial function':
// create bound responseHelper object
var responseHelper = verifyUser.bind(null, req, res, next);
// usage
responseHelper(getUsersCallback); // same as verifyUser(req, res, next, getusersCallBack);
I think you're looking to turn verifyUser into a middleware function.
function verifyUser (req, res, next) {
var user = // yadda yadda session stuff
if (user) {
req.user = user; // [1] what you do to the req object here...
} else {
return res.status(401).send({ message: "No way Smokey Joe"});
/**
* alternatively, do something like
* var err = new Error("Not authorized");
* err.statusCode = 401;
* return next(err);
*
* this will kick off Express' error handling mechanism,
* which you should read about in the docs (see the link below)
*/
}
next();
// very important to call next after this verifyUser has done its job
// if you don't, the next middleware won't go off,
// and the request will just hang
}
function getUsers (req, res, next) {
// [2] will show up on the req object here, assuming you chain these
// two functions together as middleware
}
app.get("/users", verifyUser, getUsers);
app.get("/services", verifyUser, getServices);
// here's a route that needs no session auth, so no need to verifyUser
app.get("/latest-posts", getLatestPosts);
When you tell Express to use a function or attach a function to a route path via get('/my/route', hanlderFun) or some such, you've basically turned handlerFun into a middleware.
You can define however many middleware as handlers on a route as you like, and they'll all execute in turn as long as you keep calling next.
app.post("/checkout", verifyUser, tallyCart, checkInventory, doPayment, sendInvoice);
The job of next is to pass control from the current middelware to the next one. It's an object
You can do other stuff with next, too, which you should read up on in the docs.
http://expressjs.com/en/guide/writing-middleware.html
http://expressjs.com/en/guide/using-middleware.html
The docs on routing have good info on middleware as well:
http://expressjs.com/en/guide/routing.html
For extra credit, check out error handling middleware, too:
http://expressjs.com/en/guide/error-handling.html
In express, everything else held constant, is there a difference between:
app.all('/', mongoProxy(config.mongo.dbUrl, config.mongo.apiKey));
and
app.all('/', function (req, res) {
mongoProxy(config.mongo.dbUrl, config.mongo.apiKey);
});
The former is able to return the return value from mongoProxy while the latter is not, where mongoProxy looks something like this:
module.exports = function(basePath, apiKey) {
basePath = url.parse(basePath);
// Map the request url to the mongolab url
// #Returns a parsed Url object
var mapUrl = module.exports.mapUrl = function(reqUrlString) {
//use the basePath to Parse the URL
return newUrl;
};
var mapRequest = module.exports.mapRequest = function(req) {
var newReq = mapUrl(req.url);
// Make a new request and return it..
return newReq;
};
var proxy = function(req, res, next) {
try {
var options = mapRequest(req);
// Create the request to the db
var dbReq = https.request(options, function(dbRes) {
// Save result
});
// { send result }
res.send(data);
res.end();
});
});
// send request
dbReq.end(JSON.stringify(req.body));
} catch (error) {
//..
}
};
return proxy;
};
The documentation is not clear on explaining the conceptual difference between the two; in the examples I've seen, the former function
app.all('/', mongoProxy(config.mongo.dbUrl, config.mongo.apiKey));
is able to access the req and res object without having it actually passed in as done in the latter, function (req, res).
What is the difference between the two, and is one preferable?
tl;dr
Yes, there is a difference: the first will work, whereas the second will hang (you don't invoke the anonymous function returned by mongoProxy). The first is preferable because it is more idiomatic to express (you're using middleware).
First, note how in mongoProxy, you return proxy, an anonymous function:
module.exports = function(basePath, apiKey) {
/* snip */
var proxy = function(req, res, next) { // <-- here
/* snip */
};
return proxy; // <-- and here
};
Let's break it down:
var proxy = mongoProxy(config.mongo.dbUrl, config.mongo.apiKey)
// proxy is an anonymous function which accepts: (req, res, next)
app.all('/', proxy);
// express will use proxy as the callback (middleware), which means this is the same as:
app.all('/', function (req, res, next) {
proxy(req, res, next)
})
Let's rewrite the second example—which should make clear why it doesn't work:
var proxy = mongoProxy(config.mongo.dbUrl, config.mongo.apiKey)
app.all('/', function (req, res) {
proxy // nothing happens because you don't invoke the function
});
If you want to use the second example, you could invoke proxy with proxy(req, res, next), but this is not idiomatic (in general and especially for express). Express is all about middleware, so use the first example.
Here's another example, which uses a closure (much like your mongoProxy function):
function getPermissionLevelMiddleware (level) {
// returns an anonymous function which verifies users based on `level`
return function (req, res, next) {
if (req.isAuthenticated() && req.user.permission.level > level)
return next()
return res.redirect('/no/permission')
}
}
var isAdmin = getPermissionLevelMiddleware(9000)
// `isAdmin` only allows users with more than 9000 `user.permission.level`
var isPleb = getPermissionLevelMiddleware(1)
// `isPleb` allows users with more than 1 `user.permission.level`
app.get('/admin', isAdmin, function (req, res) {
res.render('admin.jade')
})
Obviously the first one will return result because req and res objects are accessible to it, where as second case you need to send req and res in the method params of mongoProxy. If you won't send, req and res won't be accessible to it. So, for second scenario to work, the method signature need to be changed to :
module.exports = function(basePath, apiKey, req, res) {
I have done
I have a Node js Express app where I am setting cookies from many different places (the server, Twitter, another database, etc). These all need to happen in series. To avoid a callback nightmare, I am trying to use Async.js. But, the response keeps getting sent BEFORE the async.series call finishes. How can I make sure the async.series completes and THEN the res.redirect is called?
CALL TO MODULE WITH ASYNC
// OTHER CODE
accountCookies(req, res, next); // THE MODULE WITH ASYNC
res.redirect(302, 'https://' + req.host + '/account');
ASYNC MODULE
module.exports = function (req, res, next) {
req.appVariables.error = null;
// Required modules
var async = require(__dirname + '/utilities-----async.js'); // Utility for controlling node's asynchronous behavior
var userSessionObject = {};
// ...OTHER MODULES
// Cookie values set by the server
var cookieValuesSetByTheServer = function(req, res, next, userSessionObject, callback) {
userSessionObject.STUFF = STUFF;
logs.dev('cookieValuesSetByTheServer');
setTimeout(function(){callback(req.appVariables.error, '1');}, 3000);
};
// Cookie values from Twitter
var cookieValuesFromTwitter = function(req, res, next, userSessionObject, callback) {
userSessionObject.moreSTUFF = moreSTUFF;
logs.dev('cookieValuesFromTwitter')
callback(req.appVariables.error, '2');
};
// Cookie values from User Database
var cookieValuesFromUserDatabase = function(req, res, next, userSessionObject, callback) {
userSessionObject.otherSTUFF = otherSTUFF;
logs.dev('cookieValuesFromUserDatabase');
setTimeout(function(){callback(req.appVariables.temporarilyUnavailableCalled, '3');}, 6000);
};
// Call all functions in Series
async.series([
function(callback){
cookieValuesSetByTheServer(req, res, next, userSessionObject, callback);
},
function(callback){
cookieValuesFromTwitter(req, res, next, userSessionObject, callback);
},
function(callback){
cookieValuesFromUserDatabase(req, res, next, userSessionObject, callback);
}
], function(err, results){
if ( err == null ) {
logs.dev(results);
return true;
}
else {
logs.dev(err);
return false;
}
});
};
You need to wait for async.series is done before you can send the response. The usual method of doing so is to pass a callback function:
accountCookies(req, res, next, function theCallbackFunction(success) {
res.redirect(302, 'https://' + req.host + '/account');
});
// in your async module
module.exports = function (req, res, next, theCallbackFunction) {
...
async.series([ ... ], function(err, results) {
if ( err == null ) {
logs.dev(results);
theCallbackFunction(true);
} else {
logs.dev(err);
theCallbackFunction(false);
}
});
};
In other words: you can't use return in async functions to pass a return value back to the caller.
You could try replacing the next callback with a middleware function that just does the res.redirect and then passes the request on.
accountCookies(req, res, function(req, res, next) {
res.redirect(302, 'https://' + req.host + '/account');
next();
});