42

I'd like to write an express middleware function that sets up a listener on the response's 'end' event, if one exists. The purpose is to do cleanup based on the http response code that the end handler decided to send, e.g. logging the response code and rollback/commit of a db transaction. i.e., I want this cleanup to be transparent to the end caller.

I'd like to do something like the following in express:

The route middleware

function (req, res, next) {
   res.on ('end', function () {
      // log the response code and handle db
      if (res.statusCode < 400) { db.commit() } else { db.rollback() }
   });
   next();
}

The route:

app.post ("/something", function (req, res) { 
    db.doSomething (function () {
       if (some problem) {
          res.send (500);
       } else {
          res.send (200);
       }
    });
 }

When I try this, the 'end' event handler never gets called. The same for res.on('close'), which I read about in another post. Do such events get fired?

The only other way I can think of doing this is wrapping res.end or res.send with my own version in a custom middleware. This is not ideal, because res.end and res.send don't take callbacks, so I can't just wrap them, call the original and then do my thing based on the response code that got set when they call me back (because they won't call me back).

Is there a simple way to do this?

Jake
  • 2,852
  • 7
  • 32
  • 39
  • Why don't you do the housekeeping stuff before `res.end`? – Florian Margaine Jun 21 '12 at 13:04
  • That's fine too, but I want to do it as middleware, i.e. without the end handler having to worry about the cleanup. So if I want to log the resulting response code, or rollback/commit the db transaction based on the response code, I want that to operate transparently to the end handler. So I'm trying to trap execution at some point between the end handler calling res.send and the request being done. – Jake Jun 21 '12 at 13:24
  • But **you** are defining the status code :/ – Florian Margaine Jun 21 '12 at 13:25
  • I am? I'm writing a route middleware function. The end handler defines the return code. I want to do things in response to what they decided to send. – Jake Jun 21 '12 at 13:58

3 Answers3

49

Strangely enough, it appears that the response emits a "finish" event when the response is closed: https://web.archive.org/web/20120825112648/http://sambro.is-super-awesome.com/2011/06/27/listening-for-end-of-response-with-nodeexpress-js/

Despite this blog entry being a bit old, this event still exists (Line 836 in lib/http.js), so I assume it won't disappear soon, even though neither node's nor express' documentation mention it. By early 2014 it has moved to line 504 on of lib/_http_outgoing.js and still works.

By the way, the "error" event on a server response is probably not something you'd usually want to see.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Aveius
  • 631
  • 4
  • 6
44

Just to further @Amatsuyu's response, here's how it should look:

res.on('finish', function() {
  // do stuff here
});
dmon
  • 30,048
  • 8
  • 87
  • 96
  • 1
    Is there any way to get access to the final state of res within the callback? For example, could the callback look into res and pull out data that was sent to the client? – Matthew Herbst Jul 28 '16 at 20:53
  • 2
    Yes @MatthewHerbst. Since this event listener is being handled inside the app.use call, just use the same "res" object from there. – weexpectedTHIS Aug 26 '16 at 22:19
0

Reading the documentation, here is the signature of res.send:

res.send(body|status[, headers|status[, status]])

Which means you can set your own status, like this: res.send( 'some string', 200 ); or even just res.send( 404 );.

This method is the one you use to send the response.

Once it is sent to the client, you can't access it anymore, so there is no callback.

This is the last thing your server does. Once it has processed the request, it sends the response.

However, you can access it before you send it to the client. Which means you can:

console.log( res );
res.send( datas );

If you want to rollback/commit, you do it when the database's callback is called, not when the response is gone.

Florian Margaine
  • 58,730
  • 15
  • 91
  • 116
  • Thanks. From your answer, it sounds like the way I'd like to do things is simply impossible without replacing res.send with my own version that does the cleanup then calls the real res.send. – Jake Jun 21 '12 at 14:26
  • Yes, `res.send()` is the last thing your app will do when processing the request, so if you want any cleanup you have to do it before :) – Florian Margaine Jun 21 '12 at 14:29
  • 4
    res.on ('header',func) FTW! – Hadesara Mar 19 '13 at 00:39
  • The answer is incorrect. See answers below for using res.on('finish',...) to attach an event handler to fire when the response is sent. – Lee Jenkins Jan 21 '21 at 19:37