The line between Events and Promises

In January 2014, but please bear with me this post will talk about way more updated things, I tweeted something like:

if you understand how $(document).ready(callback) works in jQuery, you basically understood the main concept behind ES6 Promises #truestory

— Andrea Giammarchi (@WebReflection) January 29, 2014

There are few comments that misunderstood that tweet, and I’ve pushed yesterday a module that rather explains what is all this about, so here my explanation.

What did that actually mean?

The cool part about events is that these can be dispatched/triggered/notified at any time.

We add a listener to any kind of event type, and we wait in a non blocking way.

// a common nodejs Emitter case
generic.on('any:event-type', function () {
  // Yeah, I got notified !!!!
});

// a common DOM handler case
generic.addEventListener('any:event-type', function (e) {
  // Yeah, `e.type` notified me !!!
});

Events are the best solution for repeated events, and other cases, including:

And probably more … however, Events suck big time at one thing:

This is true for well known events, in the DOM world, such DOMContentLoaded, on load, even on beforeunload or on unload.

This is also actually true for anything that might have been already clicked and disappeared from the view.

The solution to this “already happened“ problem? Promises!

What Promises can do that Events cannot?

Metaphorically speaking, Promises are the equivalent of any event that will remove itself once executed, and will be triggered once if set after the event already occurred, receiving the value that such event carried.

Confusing, uh? So here an example, in terms of functionality, of what an Event looks like, if it’d like to act like a Promise.

// the simplified Promise approach
// resolved at first click
var onceClicked = new Promise(
  link.addEventListener.bind(link, 'click')
);


// the simulated Event approach
// (PLEASE DON'T DO THIS AT HOME)
link.addEventListener('click', function click(e) {
  // it has been clicked once
  // we need to trap the current method
  // so we can use it later on for other events
  var aEL = link.addEventListener;
  // we need to remove this handler
  link.removeEventListener(e.type, click);
  // we redefine the method in order
  // to be able to control it later on
  link.addEventListener = function (type) {
    // whoever decided to listen to something
    // will be registered like it should be by default
    aEL.apply(link, arguments);
    // however, if this was a click, and since
    // this already happened
    if (type === 'click') {
      // we create each time a new event 
      // 'cuase we cannot dispatch the old one
      var ce = new CustomEvent(type), k;
      // ignoring things that cannot be copied
      for (k in e) {
        try {
          ce[k] = e[k];
        } catch (meh) {
          /*\_(ツ)_*/
        }
      }
      // and finally dispatch it
      link.dispatchEvent(ce);
      // remembering to remove the very same lisnter after
      link.removeEventListener.apply(link, arguments);
    }
  };
});

If you are thinking “What The Hack?“ rigth now I am with you: too many side effects and problems.

On top of that, if the user already clicked all that effort will be vane.

Truth is, even with the promise approach if the element was already clicked we won’t be notified, but the point here is that these two patterns are effectively different and if it takes that much to be Promise like for events, it takes same amount of code, if not more, and whenever is actually possible, to simulate Events through Promises.

How about the best from both patterns?

Indirectly, involuntarily, and probably quite unexpectedly, the initial tweet I’ve mentioned was exactly about this: jQuery library, capable of understanding at runtime if the document was ready or not, has been somehow providing the behavior of a Promise, invoking the callback when the document would have become ready, or instantly, in case the ready state was already understood and resolved.

To be fair though, those were different times so actually .ready(callback) cannot be compared to a Promise, since there’s no .catch() or rejection channel, and it also cannot be compared to Events, since there’ no way, once you define what you are waiting for, to come back.

Last, but not least, while we can create Promises just about anything we need, and we can also create Events for any sort of type, jQuery unique channel where such behavior was expected was the ready one.

In any case, the jQuery problem-solving/pragmatism won over any sort of argument: finally nobody had to care anymore about the status of the document so that anything loaded before, during, or after its status change, could simply be notified when the time is just about right!

Augmenting the handy pattern

I’ve been experimenting the when method via eddy-js for quite a while, and while I found it extremely handy, I’ve always felt it wasn’t that intuitive.

What I’ve actually recently achieved, in an absolute non breaking, fully backward/forward compatible way, is a little script which aim is to solve only this very specific issue: notify from the past, the present, or the future!

// the object is called `notify`
// it lets us add any listener, at any time
notify.when('data-is-ready', function (data) {
  // OK, we've got some  data here!
  console.log(data);
});



// it doesn't matter "when" data arrives
// whoever will resolve the Event 'data-is-ready'
// will notify possible listeners waiting for it
notify.that('data-is-ready', {the: 'data'});



// but what if our listener ask for data too late?
// well, the resolved data object will be sent around too
notify.when('data-is-ready', function (data) {
  // instant data, I love it!
  console.log(data); // {the: 'data'}
});

The naming convention of the API is hopefully self explicative:

Please note at the beginning I have used .about and now it’s just an alias for .that which is more imperative, as suggested in this first comment.

How about we get notified later on, in a proper asynchronous fashion?

Well, this is the main and only overload of the .that method:

// in node
fs.readFile(
  'my-setup.json',
  notify.that('fs.read:my-setup.json')
);



// before, after, or during the operation
notify.when(
  'fs.read:my-setup.json',
  function (err, json) {
    if (err) console.error('damn  it');
    else {
      var result = JSON.parse('' + json);
      console.log('Yayyyy, JSON:', result);
    }
  }
);

So how does this notify object manage handlers, work, exactly?

This, and other details about the module, in the official notify-js repository.

Which use case it this pattern useful for?

Since new patterns are usually heavier to digest, and we might end up overusing them or describing them like Barney did (a pretty accurate description if we have no context):

@WebReflection repeating promises! Anachronistic events! Love it!

— Barney Carroll (@barneycarroll) August 14, 2015

It’s good to understand why, when, and how this pattern could be handy.

Following, few use cases that are a very good fit for this pattern and are very hard to solve otherwise with just Events or Promises:

To keep in mind that every notification happens once, and only once, and if an event is triggered again only those that eventually re-registered as listeners will be notified again.

And here, all cases where notify would be a bad choice:

The only case where using notify instead of a Promise would make sense, is in very outdated browsers between IE6 and IE11, where we might not want/need a full polyfill, and keep it simple with a script which size is less than a half of a KB.

Enjoy!

Andrea Giammarchi

Fullstack Web Developer, Senior Software Engineer, Team Leader, Architect, Node.js, JavaScript, HTML5, IoT, Trainer, Publisher, Technical Editor