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):

He (casually) explains,
A Promise is an
EventEmitterwhich emits asuccessor anerrorevent
And goes on (at 26:04),
A
promise.addCallback(cb)is just API sugar forpromise.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,
resolveandreject - 
The
thenmethod addsresolveandrejectcallbacks to the Promise.- The 
resolvecallback will be called on aresolveevent - The 
rejectcallback will be called on therejectevent 
 - The 
 - 
The
catchmethod is a "catch all" to intercept anyrejectevent 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.