JavaScript: EventSource is Long Polling

UPDATE: With some serious modification to the server end point, Luke Morton was able to achieve a single open event that fires message events as responses are received via a “push” from the server: https://gist.github.com/1002722. By modifying the original example’s PHP end point, Luke’s approach was to omit the closing PHP tag (ensures that no return code is sent to the client), buffer and flush the output and wrap the whole program in a while loop that sleeps for one second to simulate delays in response activity.

Now that we’ve cleared that up, here is the original post:

Since Mozilla’s announcement this past weekend ( “Aurora 6 Is Here” ) and my own subsequent recap of previously published materials, there has been a noticeable uptick in attention towards the EventSource API, including a new polyfill for the feature.

As always, positive attention towards new JavaScript APIs is nothing but awesome — however — there seems to be a misunderstanding in the behaviour of an EventSource instance. Specifically, the idea that EventSource (in it’s current implementations) is capable of server-push, is incorrect. To demonstrate this fact, I’ve prepared a set of code examples, which can be downloaded here. Follow along with the comments in the code examples, to learn how the current implementations of the EventSource API actually work.

This is a quick look at a client that creates a single EventSource instance. The comments within are a paraphrasing of the spec production and the observed, testable behaviour.

event-source.js

document.addEventListener("DOMContentLoaded", function() {

  /*

  EventSource is nothing more then a glorified
  long polling machine. It will create HTTP requests
  to a provided url of the same origin
  (which in turn creates an `open` event ) until
  it sees a valid "text/event-stream" response body
  at which point a `message` event will be fired.

  This process will repeat until the EventSource is
  terminated my calling its close() method.

  no "data: " message from the server should result in long polling
  `open` events being fired, followed by zero `message` events

  */
  var // declare localized references
  eventSrc  = new EventSource( "event-source.php" ),
  handler = function( event ) {

    console.log( [ event.type, new Date(), event, event.data ] );

  },
  getReadyState = function( src ) {

    if ( src.readyState ) {
      // readyState is almost always 0, we're only interested in
      // seeing readyState OPEN (1) ( or CLOSED (2) )
      console.log( [ src.readyState, new Date() ] );
    }

    setTimeout(function() {
      getReadyState( src );
    }, 1);
  };

  console.log( eventSrc );

  // Setup event handlers
  [ "open", "message" ].forEach( function( name ) {

    eventSrc.addEventListener( name, handler, false );

  });

  // Begin sampling the ready state
  getReadyState( eventSrc );

}, false);

Here we have a basic EventSource end point located on the server. The comments within will detail the behaviour that is created when a response body is not provided and when it is.

event-source.php

<?php
header("Content-Type: text/event-stream\n\n");

/*
  To test the long polling thery, this response "stream"
  has no response body.

  From the client, EventSource will make an HTTP request
  for the url provided to the requesting instance. If no data body
  exists in the response, then no `message` event is fired.

  The client will continue to poll by creating new HTTP requests
  (that create `open` events on the client) until a valid
  response body ("data: [string]") is present.

  Open event-source.html in your browser, then open your console.
  you will see logged `open` events. Return to this
  file and uncomment the following line:
*/
//echo "data: foo" . "\n\n";

/*
  This will result in new `message` events being logged to the console.

  Notice that the `message` event is actually preceded by an `open`
  event, which (if you look in the network (or similar) tab of your
  console, you will see) a new HTTP request is created for
*/?>

And a simple html file to run the whole thing on your localhost:

event-source.html

Open your console.
<script src="event-source.js"></script>

Comments

We moved off of Disqus for data privacy and consent concerns, and are currently searching for a new commenting tool.

  1. EventSource does look interesting but pretty much is just an easier way to get your long polling done in newer browsers. I guess it’s push in the sense until you send the header and exit the script nothing is done on the browser side apart from wait (see my fork: https://gist.github.com/100…. So potentially it could wait until the server decides to send something, but I can’t figure out a way to send multiple messages (with delays) with one connection?

    One thing to note is you can’t get Firebug working in Aurora 6 to test this out on. I did then figure out my version of Chrome (11) supports EventSource and tested it there!

    • Thanks for updated demo and explanation! In Firefox/Aurora 6, instead of Firebug, use the \”Web Console\” for logging output. Tools > Web Developer > Web Console 🙂

  2. Small correction here. In event-source.js it says \”// seeing readyState CONNECTING (1) ( or CLOSED (2) )\” however, the spec states:

    // ready state
    const unsigned short CONNECTING = 0;
    const unsigned short OPEN = 1;
    const unsigned short CLOSED = 2;

  3. Nice! But, you’ll still notice that an \”open\” event is fired for every set of message events that are produced by the end point

  4. In my example the connection is never closed (at least in my own tests) and the open event is only trigger once, followed by subsequent message events every second.

    Of course if the script were to exit for whatever reason, the open event would be once again trigger upon reconnection. Is that what you mean?

  5. Luke – that’s pretty awesome, and I’m stoked that you figured out a way to do it in which a push effect is perceived.

    The only outstanding issue is that it required way too much \”setup\” from the server end point – which is not to say that it is incorrect – it just seems like a poorly drafted spec if this is what is required to achieve a true push.

  6. For sure it’s a bit annoying to set up PHP in this way, then again PHP wasn’t design for this kind of thing. I’m sure node.js would be an awesome fit here somewhere.

  7. Yes, doing it with PHP is painful, but with NodeJS it’s dead easy! I actually did an EventSource push example a few months back, it’s a node server that runs a sequencer to show the fingering of Smoke on the Water, you can run it in multiple windows to see that they all go in sync. All the files are in http://niiden.com/eventsour… , and yeah, sorry, I don’t have the time to change the code right now, but it does it on port 80, so you have to go sudo. :/

    Would that work with your polyfill, btw?

  8. You are not suppose to close the connection. Instead, run an infinite while loop on the server and use flush() to immediately push data to the client. Use sleep() to put some delay in the while loop.
    If the connection is closed, EventSource falls back to long polling which is a documented behaviour in the spec.

Contact Us

We'd love to hear from you. Get in touch!

Mail

P.O. Box 961436
Boston, MA 02196