Express Async - Can't set headers after they are sent, double callback? - node.js

I'm having an annoying as hell problem with 'Error: Can't set headers after they are sent.' in Express. This code originally worked using Restify rather than Express and I have been having this problem in one form or another since converting it.
The code makes 2 asynchronous requests to another API (edited out) and merges the results into a single JSON before storing it in MongoDB.
Any insight would be appreciated as I have tried all I can think of and have no idea why the same function would work in Restify and not Express with the appropriate changes.
Please don't comment on the Pyramid of Doom, I know it's not ideal, but that's not the focus here ;)
app.post('/rest/test', jsonParser, /*keycloak.protect(),*/ function (req, res, next) {
var requestObj = req.body;
try {
/* run async requests in parallel */
async.parallel([
function asyncReqV3(callback) {
request.post(reqUrl1, option_v3, function(err, response, body) {
if(err) {
console.log(err);
callback(true);
return;
} else {
callback(false, body);
}
});
},
/* v4 async request */
function asyncReqV4(callback) {
request.post(reqUrl2, option_v4, function(err, response, body) {
if(err) {
console.log(err);
callback(true);
return;
} else {
callback(false, body);
}
});
},
],
/* Collate results and send */
function collateResults(err, results) {
if(err) {
console.log(err);
return res.status(500).send("Server Error");
} else {
/* Merging results */
var hash = new Map();
results[0].body.krResult.forEach(function(obj) {
hash.set(obj.word, obj);
});
var final = results[1].body.data.map(function(obj) {
return Object.assign(hash.get(obj.word) || {}, obj);
});
var final_results = final.slice(0, 250).map(function(obj) {
return {
thing1: obj.something,
thing2: obj.somethingElse
}
});
/* Store results in DB */
db.results.insert({
request: requestObj,
response: final_results
});
res.status(200).send(final_results);
}
});
}
catch(e) {
console.log(e);
res.status(500).send({});
}
return next();
});

At the end of your route callback, remove:
return next();
You don't need it here and this line will always be executed before the async.parallel is done (collateResults in your code). You're quite likely sending a response in the subsequent routes and then again after your two requests are done.

Related

Using bind to reduce callback hell

I have lots of API data calls In the form of:
app.get('/getSomeData', function(req, res) {
//get parameters
dataService.getSomeData(param1, param2, commonCallback.bind(null, null, res));
});
var commonCallback = function (err, payload, res) {
if (err) {
console.log("server error ");
res.status(500).end();
return;
}
if (payload.messageType == 'errorMessage') {
res.status(401).json(payload);
} else {
res.json(payload);
}
}
and in DataService.js:
const getSomeData = function (param1, param2, callback) {
//do some work
if (err) {
callback(err);
return;
}
callback(null, payload);
}
exports.getSomeData = getSomeData;
but I get errors complaining that there are Cannot read property 'json' of null I don't think I'm using bind() correctly. But there doesn't seem to be many non-trivial examples out there.
What am I doing wrong?

How to return value from a file to router layer using nodejs and expressjs

i have above api in TestRouter.js
TestRouter.js
router.get('/all', function(req, resp) {
var data = reportBo.getAll();
console.log(data);
resp.status(200);
resp.send(data);
return resp;
});
i am calling getAll() from TestRouter.js to TestDao.js.
it is working fine and can fetch the data and can print in console. but i am trying to send this result to TestRouter.js and i am trying to print it on console. but it is showing undefined.
TestDao.js
module.exports.getAll = function () {
var connection = myDB.get();
connection.collection('REPORTS').find({}).toArray(function (err, result) {
if (err) {
throw err;
} else {
//console.log(result);
return result;
}
});
};
module.exports.getAll = function (callback) {
var connection = myDB.get();
connection.collection('REPORTS').find({}).toArray(function (err, result) {
if (err) {
callback(err);
} else {
//console.log(result);
callback(null, result);
}
});
};
And in your router:
router.get('/all', function(req, resp) {
reportBo.getAll(function(err, data){
if(err){
resp.status(500);
} else {
resp.status(200);
resp.send(data);
}
});
});
This way of doing things with callbacks is quite common in Node JS. Also, there is a better way called Promises. You can read up on it.

Error: can't set headers after they are sent

I want to send an argument with res.redirect(). However, I'm getting an error while running it, saying that I cannot set headers after they are sent.
What does that mean, and how can I fix it?
app.post('/updateCollaborateRequest', function(req,res) {
if(req.body.accept == true) {
Team.findOne({'name': req.body.data.teamName}, function (err, team) {
if(err) {
res.redirect('/explore');
}
team.accepted = true;
team.save(function (err) {
if (err) {
alert(err);
}
Request.findOne({'emailAdmin': req.session.email}, function(err, request) {
request.seen = true;
request.save(function(err) {
if(err) {
console.log(err);
}
});
});
res.redirect("/teamprof/" + team.name);
});
});
}
Request.findOne({'emailAdmin': req.session.email}, function(err, request) {
request.seen = true;
request.save(function(err) {
if(err) {
console.log(err);
}
res.render('userprof1', {message : req.flash('done')});
});
});
});
Your code is continuing after redirecting. That is probably the problem. You should return, otherwise you are going to keep trying to write to the HTTP response.
This particular error message is caused by code paths that lead to multiple res.xxx() calls that try to send the response more than once.
You have multiple places where you are doing that. For example, you have two res.redirect() calls inside the Team.findOne() callback, but then you proceed with Request.findOne() where you have a res.render(). You HAVE to make sure that you only send the response once.
I'm not entirely sure what the desired logic is in all cases, but you can fix that error by adding an else statement before the Request.findOne() and adding a return after each res.redirect(). If this is not the exactly flow you want, then please explain more about how you want the control flow to work. Here's the code with those changes applied:
app.post('/updateCollaborateRequest', function(req,res) {
if(req.body.accept == true) {
Team.findOne({'name': req.body.data.teamName}, function (err, team) {
if(err) {
res.redirect('/explore');
return;
}
team.accepted = true;
team.save(function (err) {
if (err) {
// FIXME: need error handling here
alert(err);
}
Request.findOne({'emailAdmin': req.session.email}, function(err, request) {
request.seen = true;
request.save(function(err) {
if(err) {
// FIXME: need error handling here
console.log(err);
}
});
});
// Are you sure you want to send this response before
// you even know if the `Request.findOne()` and `request.save()`
// have been sucessful?
res.redirect("/teamprof/" + team.name);
return;
});
});
} else {
Request.findOne({'emailAdmin': req.session.email}, function(err, request) {
request.seen = true;
request.save(function(err) {
if(err) {
console.log(err);
}
res.render('userprof1', {message : req.flash('done')});
});
});
}
});
You still have several error conditions for which no response is sent which is incomplete error handling so those need to be fixed too. And, I've added some comments in the code about some other suspect things in the code.

Node/Express function and callback are not breaking with return

I am creating a 'refresh data' function in Node and I cannot figure out where to place the callbacks and returns. The function continues to run. Below is a list of things the function should do. Could someone help out?
Check if a user has an api id in the local MongoDB
Call REST api with POST to receive token
Store token results in a MongoDB
Terminate function
./routes/index.js
router.post('/refresh', function(req, res) {
var refresh = require('../api/refresh');
refresh(req, function() { return console.log('Done'); });
});
../api/refresh.js
var callToken = require('./calltoken');
var User = require('../models/user'); // Mongoose Schema
module.exports = function(req, callback) {
User.findOne( {'username':req.body.username}, function(err, user) {
if(err) { console.log(err) }
if (user.api_id == 0) {
callToken.postToken(req.body.username, callback);
} else { // Do something else }
});
};
./calltoken.js
var request = require('request');
var Token = require('../models/token'); // Mongoose Schema
module.exports = {
postToken: function(user, callback) {
var send = {method:'POST', url:'address', formData:{name:user} };
request(send, function(err, res, body) {
if(err) { console.log(err) }
if (res.statusCode == 201) {
var newToken = new Token();
newToken.token = JSON.parse(body).access_token['token'];
newToken.save(function(err) {
if(err) { console.log(err) }
return callback();
});
}
});
}
};
I'm not an expert in Express but everywhere in you code in lines with if(err) { console.log(err) } you should stop execution (maybe of course not - up to you app) and return 400 or 500 to client. So it can be something like
if(err) {
console.log(err);
return callback(err); // NOTICE return here
}
On successful execution you should call return callback(null, result). Notice null as a first argument - it is according nodejs convention (error always goes as first argument).

NodeJS Returning Data

I have the following code which isn't returning data properly.
app.post('/login',function(req,res){
sess=req.session;
var au = authme(req.body.name,req.body.pass, function(err,data) {
if(err) {
return 'error';
}
console.log(data);
return data;
});
if(au) {
sess.username = au.name;
}
res.end('done');
});
Data is passed all the way to console.log(data); but when I try to use in in the au statement, its returning undefined.
This is the classic async problem. authme is running asynchronously so the code isn't running simply from top to bottom.
var au = authme(req.body.name,req.body.pass, function(err,data) {
console.log('I am second!')
});
console.log('I am first!')
You need to restructure the code a bit to get the desired behavior.
app.post('/login',function (req,res) {
authme(req.body.name,req.body.pass, function(err, data) {
if (err) {
return 'error';
}
if (data) {
req.session.username = data.name;
}
res.end('done');
});
});
Probably the functional call to authme is asynchronous. Put your if statement in the callback of the function call, to assure it is always validated after the asynchronous function has completed execution.
app.post('/login',function(req,res){
sess=req.session;
(authme(req.body.name,req.body.pass, function(err,data) {
if(err) {
return 'error';
}
console.log(data);
if(data) {
sess.username = au.name;
}
}))();
res.end('done');
});
In Nodejs operations are asynchronous - in short it means that result is resolved later, you don't know exactly when, while code execution goes on, not waiting for it. The way to handle such call are the callbacks. More on that topic here and here.
So with your code you fall into that classical trap. This part of the code
var au = authme(req.body.name,req.body.pass, function(err,data) {
if(err) {
return 'error';
}
console.log(data);
return data;
});
is an asynchronous call, that means that this part
function(err,data) {
if(err) {
return 'error';
}
console.log(data);
return data;
}
is a callback that runs only when result is obtained. While this part
if(au) {
sess.username = au.name;
}
is executed immediately after var au = authme(req.body.name,req.body.pass) is done.
By that moment there is no au resolved so that is why you're getting this error.
In your case you should put au check into the callback:
authme(req.body.name,req.body.pass, function(err,data) {
if(err) {
return 'error';
}
if(au) {
req.session.username = au.name;
}
res.end('done');
});

Resources