Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Promises as EventEmitters

Promises as EventEmitters

I was trying to understand JavaScript Promises by using various libraries (bluebird, when, Q) and other async approaches.

I read the spec, some blog posts, and looked through some code. I learned how to use Promises, but their internals were still a mystery involving state-machiney concepts of pending, fulfilled and rejected states, buffering thenables, and what to do if such-and-such happens but only when X but not Y unless it's Tuesday, etc.

Then by chance I had a moment of clarity when I was listening to Ryan Dahl's original Node.js presentation at JSConf 2009. There was this several-second part (click to play externally on YouTube):

![ScreenShot](http://i.imgur.com/u91DJ6P.png =400x)

He (casually) explains,

A Promise is an EventEmitter which emits a success or an error event

And goes on (at 26:04),

A promise.addCallback(cb) is just API sugar for promise.addListener('success', cb);

The mystery of Promises quickly unraveled for me. Instead of thinking of a Promise as a state machine, I saw it as simple design pattern over an EventEmitter.

In modern parlance: promise.addCallback is promise.then; success and error events are emitted when a Promise gets resolved or rejected. Yet not one of the over 50 PromisesA+ implementations creates a Promise as an extension of an EventEmitter. Conversely, here's the original formulation of Promises in Node v0.1.29 (Feb 2010).

Here's how I think about Promises now:

  • A Promise emits two events, resolve and reject

  • The then method adds resolve and reject callbacks to the Promise.

    • The resolve callback will be called on a resolve event
    • The reject callback will be called on the reject event
  • The catch method is a "catch all" to intercept any reject event upstream

That's it. Below is a super simple Promise class written in less than 50 lines of code. It's meant to be instructive, not A+ compliant, though it could be with not much work.

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

class Promise extends EventEmitter {
    // Define a Promise with a function taking two parameters:
    // a `resolve` function and `reject` function
    constructor(executor){
        super(); // Extend the EventEmitter super class

        // When `resolve` is called with a value, it emits a `resolve` event
        // passing the value downstream. Similarly for `reject`
        var resolve = (value) => { this.emit('resolve', value) };
        var reject = (reason) => { this.emit('reject', reason) };

        if (executor) executor(resolve, reject);
    }

    // Add downstream resolve and reject listeners
    then (resolveHandler, rejectHandler) {
        var promise = new Promise();

        // When a `resolve` event upstream is fired, execute the `resolveHandler`
        // and pass the `resolve` event downstream with the result
        if (resolveHandler) {
            var resolve = (data) => {
                var result = resolveHandler(data);
                promise.emit('resolve', result);
            };

            this.on('resolve', resolve);
        }

        // When a `reject` event upstream is fired, execute the `rejectHandler`
        // and pass the `reject` event downstream with the result
        if (rejectHandler) {
            var reject = (data) => {
                var result = rejectHandler(data);
                promise.emit('reject', result);
            };

            this.on('reject', reject);
        }
        else {
            // Downstream listeners always listen to `reject` so that an
            // eventual `catch` can intercept them
            this.on('reject', (data) => { promise.emit('reject', data); });
        }

        return promise;
    }

    // Handle an error from a rejected Promise upstream
    catch (handler) {
        this.on('reject', handler);
    }
}

It's interesting to go back in time and read the discussion for removing the native Node.js Promise library (as of v0.1.30) in favor of waiting for an implementation to come from the open source community (6 years later, and it's finally a standard!). The problem of callback-hell was anticipated long before it had that name. It was the stewardship of @ry to suggest waiting for a user-land solution, and to favor making Node.js a little less opinionated.

@jeveloper

This comment has been minimized.

Copy link

commented Feb 28, 2016

That is a very concise design , thank you for sharing.

@paulisaac

This comment has been minimized.

Copy link

commented Feb 28, 2016

The most accessible and useful synopsis of Promises I've read yet. Thanks.

@sinakarimi

This comment has been minimized.

Copy link

commented Feb 29, 2016

Do you think the current implementations don't wrap around an EventEmitter because they need to be compatible on browsers as well?

@medikoo

This comment has been minimized.

Copy link

commented Feb 29, 2016

Truth is that what was initially implemented in (very early version) of Node.js as promise, was actually an event emitter.

But it's not true that promise (as it's understood today, and as it was drove by A+) is an event emitter. First big difference is that when you register callback for resolve event after it occurred, it will still be called (it's not the case for event emitter). Secondly there can be only one, resolve and reject resolutions (it's not repeated events).

@dmvaldman

This comment has been minimized.

Copy link
Owner Author

commented Feb 29, 2016

@medikoo, thanks for the best explanation of differences I've heard yet

@RangerMauve

This comment has been minimized.

Copy link

commented Mar 1, 2016

How would this handle promises that are already resolved? This wouldn't work with promises that have listeners attached asynchronously. Cool way of explaining it, though.

@dmvaldman

This comment has been minimized.

Copy link
Owner Author

commented Mar 3, 2016

@RangerMauve Honestly I don't know why this is a feature of Promises (or similarly a difference between "hot" and "cold" observables). Do you find yourself needing this feature? To me it seems more like gracefully supporting an anti-pattern to make Promises more user friendly.

@MHerszak

This comment has been minimized.

Copy link

commented Jul 19, 2016

I have several emitters and I was wondering how you would "attach" those emitters to several promise? I can see a good use case for it.

@xpepermint

This comment has been minimized.

Copy link

commented Oct 1, 2016

Excelent!

@VinayaSathyanarayana

This comment has been minimized.

Copy link

commented Dec 19, 2017

Thanks... Informative....I am looking to rate limit some api calls.
Need one the following:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.