Overpromising with Multiple Promises in JavaScript

When dealing with multiple promises in JavaScript, it’s easy to “over-promise” or create too many promises. For example, let’s say you have a function that needs to return a promise because it also calls a function that returns a promise:

function doSomething() {
    var def = Q.defer();
    createPromise()
        .then(function onFulfilled() {
            console.log('first then');
            runTimeout();
        })
        .then(function onFulfilled() {
            console.log('second then');
            runTimeout();
            def.resolve();
        });
    return def.promise;
}

Instead, this function could be tightened up to look like this:

function doSomething() {
    return createPromise()
        .then(function onFulfilled() {
            console.log('first then');
            runTimeout();
        })
        .then(function onFulfilled() {
            console.log('second then');
            runTimeout();
        });
}

You don’t need to create and return a new promise. Just return the promise from the function that creates a promise. Sometimes, you can’t get away with this, but often you can.

This example also demonstrates how then returns a promise and resolves that promise only after the onFulfilled (or onRejected) function is executed. I didn’t use any onRejected functions to keep things simple.

Now, to give it more context with a contrived working example:

var i = 0;

// some function that does both synchronous and asynchronous stuff
// does not return a promise nor does it need to
function runTimeout(defObj, time) {
    time = time || 2000;
    console.log('timeout start.');
    setTimeout(function () {
        console.log('timeout finish.');
        if (defObj) {
            defObj.resolve();
        }
    }, time);
}

// some function that creates and returns a promise
function createPromise() {
    var def = Q.defer();
    console.log('createPromise ' + ++i);
    runTimeout(def);
    return def.promise;
}
 
// some function that executes a function that returns a promise
// also needs to do stuff after the promise is resolved
function doSomething() {
    return createPromise()
        .then(function onFulfilled() {
            console.log('first then');
            runTimeout();
        })
        .then(function onFulfilled() {
            console.log('second then');
            runTimeout();
        });
}

// another function that executes a function that returns a promise
// lots of nested promises at this point
function doMore() {
    console.log('start');
    doSomething()
        .then(createPromise)
        .then(function () {
            console.log('finish');
        });
}
 
doMore();

Test it in this JSFiddle.

You could argue that the functions/promises in this (naive) example could be ‘flattened,’ but when you are developing modules with dependencies on other modules, you can’t always flatten your functions into one chain of promises in one function. Furthermore, you wouldn’t want to because you’ve designed your modules to encapsulate specific logic (i.e. single responsibility principle) and that’s good.

If you’re really digging into JavaScript and promises then you ought to read the Promises/A+ specification for exact details on how then works. It’s a bit scary when you first look at it, but spec is outlined logically so it’s pretty easy to understand if you take the time.

Comments, questions and feedback welcome.