1

In order to organize code (& me being a java developer), I've divided my code into Routes & Services.

Disclaimer: I'm a nobody on Node.

Service Code:

UserService.prototype.findUser = function(userId) {
  var self = this;
  container.db.users.findOne({
    userId : userId
  }, function(err, user) {
    self.emit('userFetched', err, user);
  });
};

Router code:

var login = function(req, res) {
  service.on('userFetched', function(err, user) {
    loginAndRedirect(err, user, req, res);
  });
  service.getUser(req.body.username, req.body.password);
};

When a login request comes, I get the error:

http.js:689
    throw new Error('Can\'t set headers after they are sent.');
          ^
Error: Can't set headers after they are sent.
    at ServerResponse.OutgoingMessage.setHeader (http.js:689:11)
    at ServerResponse.res.setHeader (C:\Users\O603088\Documents\Workspace\Learning\Shory\node_modules\express\node_modules\connect\lib\patch.js:59:22)
    at ServerResponse.res.set.res.header (C:\Users\O603088\Documents\Workspace\Learning\Shory\node_modules\express\lib\response.js:518:10)
    at ServerResponse.res.location (C:\Users\O603088\Documents\Workspace\Learning\Shory\node_modules\express\lib\response.js:652:8)
    at ServerResponse.res.redirect (C:\Users\O603088\Documents\Workspace\Learning\Shory\node_modules\express\lib\response.js:694:8)
    at loginAndRedirect (C:\Users\O603088\Documents\Workspace\Learning\Shory\server\routes\auth.js:24:9)
    at UserService.<anonymous> (C:\Users\O603088\Documents\Workspace\Learning\Shory\server\routes\auth.js:14:5)
    at UserService.EventEmitter.emit (events.js:117:20)
    at C:\Users\O603088\Documents\Workspace\Learning\Shory\server\services\UserService.js:19:10
    at callback (C:\Users\O603088\Documents\Workspace\Learning\Shory\node_modules\nedb\lib\executor.js:26:17)

One of the reasons I think is because login function ends before the callback executes, hence the response has already been generated. Hence, when the redirect is executed, the about error is thrown.

However, this works:

var login = function(req, res) {
  service.getUser(req.body.username, req.body.password).on('userFetched',
      function(err, user) {
        loginAndRedirect(err, user, req, res);
      });
};

UserService.prototype.getUser = function(username, password) {
  var self = this;
  container.db.users.findOne({
    username : username,
    password : password
  }, function(err, user){
    self.emit("userFetched", err, user);
  });
  return this; // Added this for method chaining
};
  1. Why am i getting the error in the first technique?
  2. Will the second approach always work? Or is it just a coincidence that this is working?

I know the fool-proof way of doing this is to pass a callback function to findUser method and call it from within findUser method, but i don't like that approach very much.

Varun Achar
  • 14,781
  • 7
  • 57
  • 74
  • This doesn't answer your question, but the `on('userFetched')` seems incorrect. If multiple HTTP requests result in calls to `getUser`, how will the user events get paired up with the corresponding response objects? – Brandon Sep 16 '14 at 12:11
  • "but i don't like that approach very much" - but that is the correct solution and as near as I can tell, may be the only one that works correctly. – Brandon Sep 16 '14 at 12:13
  • I think there is a major memory leak in this code! – Brandon Sep 16 '14 at 12:19

2 Answers2

1

As your error shows :

 throw new Error('Can\'t set headers after they are sent.');

Means you are trying to send response two times one back the other.

May be in your code:

var login = function(req, res) {
  service.on('userFetched', function(err, user) {
    loginAndRedirect(err, user, req, res);-----------------------> 1st response sent
  });
  service.getUser(req.body.username, req.body.password);-------------> 2nd response sent.
};

Check for whether you are sending response simultaneously..

Subburaj
  • 5,114
  • 10
  • 44
  • 87
  • Yes.. i already guessed that.. Your answer seems to confirm it. But why does the second technique work? Its basically the same thing. – Varun Achar Sep 16 '14 at 11:20
  • Refer this link http://stackoverflow.com/questions/7042340/node-js-error-cant-set-headers-after-they-are-sent?rq=1 – Subburaj Sep 16 '14 at 11:25
1

Will the second approach always work? Or is it just a coincidence that this is working?

I think it's a coincidence. The approach is the same. The reason why you are having problems is that you have a concurrency problem. If you only make one request at a time, this will work fine. If you have multiple requests which overlap, then you may run into this problem.

I think the issue is with this line:

service.on('userFetched', function(err, user) {
  loginAndRedirect(err, user, req, res);
});

The problem with this approach is that callback will fire when any userFetched event is emitted, not just the one corresponding to the request you have a closure around. When you call .on, you are giving the UserService a reference to your callback and it keeps the reference. In fact, it will keep it forever.

So, request 1 comes in, asks for a user, then listens for userFetched event, receives the event, and sends the data to response 1. So far so good.

Then request 2 comes in, asks for a user, then listens for userFetched event. The callback for request 1 sends the data to response 1, then the callback for response 2 sends the data to response 2. The first part will throw an error (the one you reported above). Depending on how the error handling is setup, the response 2 part may or may not actually occur.

Since every request adds an event listener and never removes it, eventually your heap will fill up with event listeners, all of which have closures around the request and response objects, preventing them from being garbage collected.

My guess is that if you run this program long enough, you will get the JavaScript equivalent of an OutOfMemoryError.

I know the fool-proof way of doing this is to pass a callback function to findUser method and call it from within findUser method, but i don't like that approach very much.

If you are struggling with a way that won't work and know a way that works and is the documented, known, good way to do it, then why are you fighting it? A one-time callback is exactly the way to solve this problem. You're making your life harder by fighting it.

Brandon
  • 9,822
  • 3
  • 27
  • 37
  • Wow.. you were right about the first part.. It fails on the second user. But, if i change the `.on` to `.once`, it works. How much of a performance impact will this have? And will the memory be freed? – Varun Achar Sep 16 '14 at 13:29
  • `.once` should address the memory leak, but not fix the functionality. You are asking for a user and then saying "next time you find a user, send him to me". It may not be the user you asked for if you have concurrent load. – Brandon Sep 16 '14 at 13:45
  • Could you please elaborate the "one-time callback"? I have a similar issue and am trying identify the problem, respectively solve the issue. Thanks a lot! – Tobi Jan 02 '15 at 19:12
  • 1
    @Tobi A callback is a function that you pass to an asynchronous call. The callback will be invoked when the operation completes. My recommendation is to simply pass a callback rather than trying to use an event emitter because async functions remember a reference to the callback and the callback alone. Event emitters are designed to broadcast a message to multiple independent, long-lasting subscribers. – Brandon Jan 02 '15 at 19:38
  • @Brandon Thanks a lot for your fast feedback, much appreciated! I know some NPM packages which make extensive use of EventEmitters. A good example would be Restler (https://github.com/danwrong/restler). It's using Events for single http calls, so I wonder if this is possible via correct scoping, or some other techniques I got wrong. I'm trying to build a cluster-enabled RPC system with ZeroMQ and Node (https://github.com/dotcloud/zerorpc-node) via the Router/Dealer model, and try to use the `socket.on("message", function(data){...})` for dispatching the calls to the actual worker functions. – Tobi Jan 03 '15 at 18:44
  • 1
    @Tobi That works great if the event emitter itself is tied to the request. For example, socket.io uses event emitters exclusively, but the listeners are added to the socket itself. You could do something similar with a database query if the listener is bound to *that specific* query execution event, rather than any statement. – Brandon Jan 03 '15 at 19:41