uglycode
uglycode

Reputation: 3082

Express.js - while loop before sending response

I'm trying to implement and existing solution in node.js, specifically, using express.js framework. Now, the existing solution works as follows:

So what basically happens is, the client connects and the connection is active (in a loop) until enough clients connect, then and only then there is a response (to all clients at the same time).

Now I'm not expert in these architectures, but from what I think, this is not a correct or good solution. My initial thought was: this must be solved with sockets. However, since the existing solution works like that (it's not written in node.js), I tried to emulate such behaviour:

var number = (function(){
var count = 0;
  return {
    increase: function() {
        count++;
    },
    get: function(){
        return count;
    }
  };
})();

app.get('/test', function(req, res){
  number.increase();
  while (number.get() < 3) {
  //hold it here, until enough clients connect  
  }
  res.json(number.get());
});

Now while I think that this is not a correct solution, I have a couple of questions:

  1. Is there any alternative to solving this issue, besides using sockets?
  2. Why does this "logic" work in C#, but not in express.js? The code above hangs, no other request is processed.
  3. I know node.js is single-threaded, but what if we have a more conventional service that responds immediately, and there are 20 requests all at the same time?

Upvotes: 1

Views: 2660

Answers (2)

robertklep
robertklep

Reputation: 203554

I would probably use an event emitter for this:

var EventEmitter = require('events').EventEmitter;
var emitter      = new EventEmitter();

app.get('/', function(req, res) {
  // Increase the number
  number.increase();

  // Get the current value
  var current = number.get();

  // If it's less than 3, wait for the event emitter to trigger.
  if (current < 3) {
    return emitter.once('got3', function() {
      return res.json(number.get()); 
    });
  }

  // If it's exactly 3, emit the event so we wake up other listeners.
  if (current === 3) {
    emitter.emit('got3');
  }

  // Fall through.
  return res.json(current);
});

I would like to stress that @Plato is correct in stating that browsers may timeout when a response takes too much time to complete.

EDIT: as an aside, some explanation on the return emitter.once(...).

The code above can be rewritten like so:

if (current < 3) {
  emitter.once('got3', function() {
    res.json(number.get());
  });
} else if (current === 3) {
  emitter.emit('got3');
  res.json(number.get());
} else {
  res.json(number.get());
}

But instead of using those if/else statements, I return from the request handler after creating the event listener. Since request handlers are asynchronous, their return value is discarded, so you can return anything (or nothing). As an alternative, I could also have used this:

if (current < 3) {
  emitter.once(...);
  return;
}
if (current === 3) {
...etc...

Also, even though you return from the request handler function, the event listener is still referencing the res variable, so the request handler scope is maintained by Node until res.json() in the event listener callback is called.

Upvotes: 3

Plato
Plato

Reputation: 11072

  1. Your http approach should work
  2. You are blocking the event loop so node refuses to do any other work while it is in the while loop
  3. You're really close, you just need to check every now and then instead of constantly. I do this below with process.nextTick() but setTimeout() would also work:

var number = (function(){
var count = 0;
  return {
    increase: function() {
        count++;
    },
    get: function(){
        return count;
    }
  };
})();

function waitFor3(callback){
  var n = number.get();
  if(n < 3){
    setImmediate(function(){
      waitFor3(callback)
    })
  } else {
    callback(n)
  }
}

function bump(){
  number.increase();
  console.log('waiting');
  waitFor3(function(){
    console.log('done');
  })
}

setInterval(bump, 2000);
/*
app.get('/test', function(req, res){
  number.increase();
  waitFor3(function(){
    res.json(number.get());
  })
});
*/

Upvotes: 1

Related Questions