5

I have a Node server that uses Connect to insert some middleware which attempt to transform a response stream from node-http-proxy. Occasionally this transformation can be quite slow and it would be preferable in such cases to simply return a response that doesn't include the transformations or alternatively includes their partial application.

In my application I've attempted to use setTimeout to call next after some number of milliseconds in the context of the transformation middleware. This generally works but exposes a race condition where if the middleware has already called next and then setTimeout fires and does the same an error occurs that looks like: Error: Can't set headers after they are sent.

Eventually I evolved the setTimeout to invoke next with an Error instance as its first argument and then later on in my middleware chain would catch that error and assuming res.headersSent was false would start sending the response via res.end.call(res). This worked and surprisingly I could set the timeout to nearly nothing and the response would happen significantly faster and be complete.

I feel like this last method is a bit of a hack and not immune from the same race condition, but perhaps appears to be a little more resilient. So I would like to know what sort of idiomatic approaches Node and Connect have for dealing with this kind of thing.

How can I go about timing out slow middleware and simply return the response stream?

Currently this seems to do what I want, more or less, but again feels a bit gross.

let resTimedout = false;
const timeout = setTimeout(() => {
  if (!resTimedout) {
    resTimedout = true;
    next();
  }
}, 100);


getSelectors(headers, uri, (selectors) => {
  const resSelectors = Object.keys(selectors).map((selector) => {
    ...
  };

  const rewrite = resRewrite(resSelectors);
  rewrite(req, res, () => {
    if (!resTimedout) {
      resTimedout = true;
      clearTimeout(timeout);
      next();
    }
  });
});
aynber
  • 22,380
  • 8
  • 50
  • 63
maxcountryman
  • 1,562
  • 1
  • 24
  • 51
  • You can look at `Promise.race` and `async.race` http://bluebirdjs.com/docs/api/promise.race.html , http://caolan.github.io/async/docs.html#race – Sangharsh Jan 27 '17 at 18:19

3 Answers3

0

setTimeout returns the id of the timeout, so you can then run clearTimeout passing in the id. So when the transformation is complete just clear the timeout before you call next.

var a = setTimeout(()=>{}, 3000);
clearTimeout(a);

https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout

YourGoodFriend
  • 943
  • 15
  • 22
0

Use async.timeout or Promise.timeout from Bluebird or Q library

Sangharsh
  • 2,999
  • 2
  • 15
  • 27
0

You could eliminate the need for global variables and decide this per request:

const rewrite = resRewrite(resSelectors);
rewrite(req, res, () => {
    // set a timer to fail the function early
    let timer = setTimeout(() => {
        timer = null;
        next();
    }, 100);

    // do the slow response transformation
    transformResponse((err, data) => { // eg. callback handler
        clearTimeout(timer);
        if (timer) next();
    });
});

How it works

If the timer ends first, it sets itself to null and calls next(). When the transform function ends, it will see the timeout is null and not call next().

If the response transform is faster, it clears the timeout to prevent it running next later on.

repoleved
  • 744
  • 6
  • 11