Arpith Siromoney 💬

Babel-node, ES6, and how I finally used promises

I’ve been working on an SSE server and wanted to test how many connections it could accept. The eventsource library seemed like a simple way to create SSE clients, so I wrote a small nodejs app to create a bunch of connections to a channel, send a message and print ‘WORKS’ if they all got it. You can run it by cloning the repo ($ git clone https://github.com/arpith/sse-test) and then, in the directory, running $ babel-node main.js

    
    var EventSource = require('eventsource');
    var request = require('request');

process.env lets you read environment variables, in this case a maximum number of clients, a token (for the POST request to the SSE server) and a URL for the server.

    
    var maxClientCount = process.env.CLIENT_COUNT;
    var token = process.env.TOKEN;
    var satelliteUrl = process.env.SATELLITE_URL + "/broadcast";

Defining a class in ES6 is pretty straightforward, writing a constructor method lets you initialise the object (this.es is the actual SSE client).


    class Client {
      constructor(url) {
        this.es = new EventSource(url);
      }
      receiveMessage(message) {
        return new Promise((resolve, reject) => {
          this.es.onmessage = (m => {if (m.data == message) resolve();});
        })
      }
      close() {
        this.es.close();
      }
    }


There are two things to note about receiveMessage(): one is the two arrow functions (i.e., ((arg1, arg2) => { statement;}) instead of function(arg1, arg2) { statement; }) that I’ve used, and the other is the Promise (read more) that it returns. The first arrow function (passed to the Promise object) is written this way so that the this in this.es.onmessage refers to the this of the class, while the second arrow function (assigned to this.es.onmessage) is just to keep things brief.

I used a promise so that I could do something when the client receives the sent message (or rather, when all the clients in the test do). A promise object takes a function that gets passed two functions as arguments, resolve and reject.

When things work out (in this case, one of the messages received by the eventsource object is the message we are interested in) call resolve() and the promise will resolve (and whatever you want to do next can take place).

reject is a function you can call if things don’t work out (and you want to pass on the error, for example). I’m not using it because eventsource clients conveniently retry connections, so an error isn’t really a failure. Skip to the end if you want to see how this promise (that we’re returning) is used.

I need a random string to use as the channel name:


    var randomString = function() {
      var crypto = require('crypto')
      , shasum = crypto.createHash('sha1');
      shasum.update(Math.random().toString());
      return shasum.digest('hex');
    }

And a random number of clients to try sending a message to:


    var randomClientCount = function() {
      return getRandomInt(1, maxClientCount);
    }

    var getRandomInt = function(min, max) {
      return Math.floor(Math.random() * (max - min)) + min;
    }

And here’s the actual test, where I create a bunch of clients and store them in an array, before using Promise.all to log “WORKS” if all the clients receive the message. Promise.all takes an array of promises, and returns a promise that has a then method that takes two functions, the first one to be called if Promise.all() resolves and a second one that will be called if it doesn’t (someone used reject()).


    var startTest = function () {
      var msg = 'PONG'
      var channelUrl = satelliteUrl+'/'+randomString();
      var clientCount = randomClientCount();
      var clients = [];
      
      for (let i=0; i<clientCount; i++) {
        clients.push(new Client(channelUrl));
      }
      
      Promise.all(clients.map(c => c.receiveMessage(msg)))
      .then(function(response) {
        console.log("WORKS for "+clientCount+" clients");
        clients.map(c => c.close());
      }, function(error) {
        //this function won't be called, we haven't used reject anywhere
      });
     request.post(channelUrl).form({'token':token,'message':msg});
    }

As mentioned earlier, all this is to be able to log when all clients have received the message that is now going to be POST’d. Now I just have to run test() every second or so.


    setInterval(function(){startTest()}, 1000);