If you aren’t familiar with Promises or you have used them but don’t know why, then I hope this article will help you. Because “Promises are confusing.” And like any programming construct, having a good grasp of its behaviour comes from understanding what a Promise is built to do, not just how to use them. In this article I want to discuss some ways to utilise the “power of the Promise” and explain that in some cases unexpected or unusual behaviour is a feature of the language, not a “bug in the Matrix”. We will also discuss some common anti-patterns; what they are, why they are potentially dangerous, and how to avoid them.

Give us this day our daily Promise,
and forgive us our asynchronicity,
as we forgive those who nested too many callbacks,
and lead us not into Callback Hell,
but deliver us from evil,
for thine is the JavaScript,
and the power, and the glory,
for ever and ever,
ECMA

Glossary

1.0 Promises
2.0 Ponderings
   2.1 “What do resolve and reject mean?”
   2.2 “What is the difference between new Promise() and Promise#then?”
   2.3 “What can I give Promise#then? Does it accept a new Promise?”
   2.4 “What happens if I don’t resolve a Promise?”
   2.5 “What affect does rejecting a Promise have?”
   2.6 “If I throw an Error, where will it go?”
   2.7 “Should I throw an Error in a new Promise() or call the reject function?”
   2.8 “Is throwing the same as rejecting?”
   2.9 “Why isn’t my Promise‘s error handler being invoked?”
   2.10 “Why is my Promise ‘pending’ when I have already resolved it?”
   2.11 “Generally, what rules should I follow for handling errors?”
3.0 Patterns
   3.1 Batching with Array#map and Promise.all
   3.2 Clean Chaining
   3.3 Throwing Instead of Rejecting
4.0 Anti-Patterns
   4.1 Deferring a Promise
   4.2 Nesting Promises
   4.3 The Non-Returning Promise
5.0 Apologies
6.0 References

1. Promises

Why? Asynchronous development means passing control between multiple contexts frequently. This results in quite confusing code, as anonymous function declarations dominate the codebase and make it difficult to follow. Promises are a way of organising asynchronous operations in such a way that they appear synchronous and we can therefore leverage them to save us from feared Callback Hell [1].

Patience is a virtue
1
2
3
4
5
6
7
8
9
new Promise(function(resolve, reject) {
console.log('Are ya ready, kids?!');
setTimeout(resolve, 1000 * 12);
})
.then(function(result) {
// twelve seconds later
// https://www.youtube.com/watch?v=qIX1BhvUMJ0
console.log('Aye, aye, Capt\'n!!!');
});

How? Promises are a part of the ES6 specification [2]. This means they are available natively in implementations of the spec or alternatively, for environments that haven’t caught up yet [3], in a plethora of third-party libraries [4]. My favourite is bluebird for its fantastic array of features and the famous promisify function [5,6]. To run the examples given in this article I suggest copy-pasting into the Chrome developer console.


2. Ponderings

As humans we are all Ponderlings, each of us accustomed to a good old ponder now and again. It is the lifeblood of momentum in learning and carries us from bemused dumb-brains to clever-little-things. Here are some ponderings that I pondered as a Promise Ponderling… I hope that made sense… and I hope they will help you.

2.1 “What do resolve and reject mean?”

To understand what these terms mean we only have to consider why Promises borrow their name from their real-world counterpart, whose purpose is as an assurance mechanism. In the real-world a promise has two outcomes: that the assurance is fulfilled, or it is unfulfilled. We can map the actions resolve and reject as arbiters of each outcome, respectively:

    ACTION   =>   OUTCOME
    -------------------------
    resolve  =>   fulfilled
    reject   =>   unfulfilled

2.2 “What is the difference between new Promise() and Promise#then?”

We can create a new assurance by using new Promise(). The function that Promise accepts will eventually call either the resolve or reject functions, which are given as arguments. Promise#then is an instance method, allowing you to accept the result or failure of a Promise while itself returning a new Promise. As a result we can chain calls to then, each invoked when its immediate ancestor has resolved or rejected. It is this chaining that cleverly brings asynchronous architecture back to the familiar linear behaviour of synchronous development.

Creating and accepting the result of a Promise
1
2
3
4
5
6
7
8
9
10
11
// Create a new 'assurance'
// ------------------------
let promise = new Promise(function(resolve, reject) {
resolve('Assurance fulfilled!');
});

// Attach a 'listener' to accept the success of the assurance
// ----------------------------------------------------------
promise.then(function(result) {
console.log(result); // => 'Assurance fulfilled!'
});

2.3 “What can I give Promise#then? Does it accept a new Promise?”

The then method accepts two callables: a handler for success and a handler for ‘catching’ errors (read on a few questions to find out what errors). Both callables are handed a single value on invocation. If you don’t give then a callable the Promise will resolve immediately to the previous Promise’s value. new Promise(...) is not a callable, and so this rule will come into effect.

Well, well, well... what do we have here then?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let promise1 = new Promise(function(resolve, reject) {
resolve('Hello, World!');
})
.then(function(result) {
console.log(result); // => 'Hello, World!'
});

let promise2 = new Promise(function(resolve, reject) {
resolve('Hello, World!');
})
.then(
// incorrectly give a Promise
new Promise(function(resolve, reject) {
resolve('In b4 Hello, World!');
})
)
.then(function(result) {
console.log(result); // => 'Hello, World!'
});

2.4 “What happens if I don’t resolve a Promise?”

Then you take it to your grave! (Or to Promise Hell?) But really, nothing happens. Execution will not be passed to the next then and your Promise chain will become ‘stuck’.

Hang on to your resolve
1
2
3
4
5
6
7
8
new Promise(function(resolve, reject) {
// Attempt to resolve by conventionally returning
return 'Hello, World!' // echo... echo... echo...
})
.then(function(result) {
// never invoked
console.log('result');
});

However don’t forget that inside a then success handler, you can return any value to pass execution on to the next then in the Promise chain:

Say hi, World
1
2
3
4
5
6
7
8
9
10
11
12
13
function helloWorld() {
return new Promise(function(resolve, reject) {
resolve('Hello, World!');
});
}

helloWorld()
.then(function(result) {
return result; // => pass 'Hello, World!' to the next handler
})
.then(function(result) {
console.log(result); // => 'Hello, World!'
});

2.5 “What affect does rejecting a Promise have?”

Rejecting a Promise will invoke the next available error handler supplied with then.

It isn't you, it's me...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function rejectPromise() {
return new Promise(function(resolve, reject) {
reject(new Error('Error! Error!'));
});
}

// Supplying both success and error handlers
// -----------------------------------------
rejectPromise()
.then(function(result) {
// never invoked
console.log(result);
}, function(err) {
console.log(err); // => Error: 'Error! Error!'
});

// Supplying just an error handler
// -------------------------------
rejectPromise()
.then(function(result) {
// never invoked
console.log(result);
})
.then(
null, // no success handler given
function(err) {
console.log(err); // => Error: 'Error! Error!'
}
);

// Catching an error using the `catch` method on a Promise
// -------------------------------------------------------
rejectPromise()
.catch(function(err) {
console.log(err); // => Error: 'Error! Error!'
});

2.6 “If I throw an Error, where will it go?”

It’s up to you to give errors a place to go by supplying an error handler using then. You can supply an error handler with each call to then, but the handler will only be invoked when an error occurs before and outside of its own then.

Indiana Jones and the Search for the Lost Error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
new Promise(functiion(resolve, reject) {
// (1)
resolve('ONE');
})
.then(function(result) {
// (2)
console.log(result); // => 'ONE'
throw new Error('Error! Error!');
}, function(err) {
// never invoked because (1) doesn't throw an Error
console.log(err);
})
.then(function(result) {
// never invoked because (2) throws an Error
console.log(result);
}, function(err) {
// (2)'s error is caught here
console.log(err); // => Error: 'Error! Error!'
})
.then(
function(result) {
throw new Error('Another Error! Run for it!'); // This Error has nowhere to go...
}
// no error handler
);

2.7 “Should I throw an Error in a new Promise() or call the reject function?”

It depends. Throwing an error will stop execution within the function but calling reject will not stop execution. However both will mark the Promise as ‘rejected’ so you cannot perform both and expect the error handler to be called twice. An error handler will only ever be called once.

Should I stay or should I throw now...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Allow execution to continue by calling reject
// ---------------------------------------------
new Promise(function(resolve, reject) {
reject('Error! Error!');
console.log('Keep on keeping on...'); // this is still called
})
.then(
null, // no success handler
function(err) {
console.log(err); // => Error: 'Error! Error!'
}
);

// Prevent execution from continuing by throwing
// ---------------------------------------------
new Promise(function(resolve, reject) {
throw new Error('Error! Error!');
console.log('Silence...'); // never invoked
})
.then(
null, // no success handler
function(err) {
console.log(err); // => Error: 'Error! Error!'
}
);

If you are calling a non-callback and non-Promise procedure that isn’t in your control and that throws an error traditionally, you can wrap it in a try/catch block. (Read the next question to understand why you cannot do this for a procedure that throws an error asynchronously - this will almost never be the case; it will instead ask for an error-first callback or return a Promise.)

If at first you don't succeed, try and catch again
1
2
3
4
5
6
7
8
9
10
11
12
13
14
new Promise(function(resolve, reject) {
try {
// this throws Error('Error! Error!') synchronously
syncThrow();
} catch(err) {
reject(err);
}
})
.then(
null, // no success handler
function(err) {
console.log(err); // => Error: 'Error! Error!'
}
);

2.8 “Is throwing the same as rejecting?”

No. But yes. To the handler, there will be no difference, however you cannot throw asynchronously! This is why we are given the reject function to begin with! Placing an asynchronous operation within a try/catch does not guarantee that a thrown error will be caught by the catch block, because the asynchronous procedure will actually execute outside of the try block.

Is throwing the same as rejecting?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Attempt to catch an asynchronously thrown error using traditional try/catch block
// ---------------------------------------------------------------------------------
try {
// this throws Error('Error! Error!') asynchronously
asyncThrow();
} catch(err) {
// never invoked
console.log(err);
}

// Attempt to catch an asynchronously thrown error within a Promise
// ----------------------------------------------------------------
new Promise(function(resolve, reject) {
try {
asyncThrow(); // this throws Error('Error! Error!')
} catch(err) {
// pass `err` onto the error handler below
// ...doesn't work as seen above!
reject(err);
}
})
.then(
null, // no success handler
function(err) {
// never invoked because the error above
// was never caught...
console.log(err);
}
);

2.9 “Why isn’t my Promise‘s error handler being invoked?”

A common mistake when first starting to use Promises is placing the handler for catching errors within the same call to then where one of those errors may be thrown.

Good catch!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
new Promise(function(resolve, reject) {
resolve('Nothing to see here... ');
})
.then(function(result) {
throw new Error(result + ' Error! Error!');
// or Promise.reject(result + ' Error! Error!');
}, function(err) {
// never invoked
console.log(err);
})
.then(
null, // no success handler given
function(err) {
console.log(err); // => 'Nothing to see here... Error! Error!'
}
);

2.10 “Why is my Promise ‘pending’ when I have already resolved it?”

This is my most ‘pondiferous’ finding (or interesting for those less versed than I in the language of Ponderers): all Promise handlers are executed asynchronously! What does this mean? Let’s take a look…

1
2
3
4
let p = Promise.resolve().then(new Array()); // (1)
console.log(p); // (2) status: PENDING
// ...wait...
console.log(p); // (3) status: RESOLVED

What’s going on?

(1) We create a resolved Promise using Promise#resolve and attempt to create a handler to accept the success of this Promise, but give it a a non-callable. In this case, a new Array.

(2) Then log the return of the statement (add both 1 and 2 to the call stack at the same time). This executes before the Promise attempts to call the success handler given to it with Promise#then, as a Promise waits for the call stack to be empty before invoking all handlers.

(3) Finally, once the call stack is empty, the Promise resolves as it accepts the result of the call to Promise#resolve because a new Array is not callable.

2.11 “Generally, what rules should I follow for handling errors?”

Always catch errors using the Promise#catch instance method at the end of any Promise chain.

Gotta catch `em all!
1
2
3
4
5
6
saveUser()
.then(notifyUserOfSuccess)
.catch(function(err) {
logError(err);
notifyUserOfError(err);
});

And throw errors where they useful to the user or for debugging. If something has failed to save and you are auditing, for example, do you need to tell the user that it failed to save half-way through the process? Or just that it has failed to save altogether? Probably the former is most appropriate for debugging and the latter for the user.


3. Patterns

The Promise object has a number of static and instance methods that used together with methods available on other objects within the JavaScript global namespace (e.g. Array) allow us to create powerful aysnchronous behaviour. We call these patterns as these behaviours will be used multiple times within a single application and across any number of independent applications.

3.1 Batching with Array#map and Promise.all

It is quite common to want an action performed after two or more other actions complete. Often you will be performing the same operation multiple times with different arguments.

How? You may have come across the Array#map method already. It’s a great way of turning a collection of things into a collection of other things. As an example, let’s turn an array of incorrectly spelt words into an array of Promises that will ‘spell-check’ each word and return the correct word. We will then use Promise#all to log the result of each Promise, but only once each Promise has been resolved.

The Not-really-spell-checking Spell Checker
1
2
3
4
5
6
7
8
9
10
let promises = ['Wunt', 'Hullo', 'Sunt', 'Lut'].map(function(word) {
return new Promise(function(resolve) {
let correctSpelling = word.replace(/u/, 'e');
setTimeout(resolve.bind(null, correctSpelling));
});
});

Promise.all(promises).then(function(words) {
console.log(words); // => Array['Hello', 'Went', 'Spent', 'Let']
});

3.2 Clean Chaining

Write clean code by chaining function references instead of giving anonymous handlers to then. By giving a function reference at each step of the way you can make it much clearer what the code is doing to whoever is reading your code.

Chaining Function References
1
2
3
4
validateNewUser(user)
.then(saveUser)
.then(notifySuccess)
.then(redirectUserToDashboard);

3.3 Throwing Instead of Rejecting

If you are writing large applications you might find it useful to start throwing errors traditionally instead of calling Promise#reject when an error occurs. The best reasons I can think of for adopting this pattern are that (1) you can write custom Errors and (2) you don’t have to remember to call Promise#reject.

Throwing Custom Errors
1
2
3
4
5
6
7
function validateUserCredentials(username, password) {
return database
.findUser(username, password)
.then(null, function(err) {
throw new ValidationError(); // custom Error
});
}


4. Anti-patterns

Anti-patterns are the charlatan of the programming world - created when a particular programming tool or construct is used to solve the wrong problem whilst doing so in a semantic and seemingly ‘complete’ manner, they of course appear to lack any negative consequence, but in reality will have subtle and potentially harmful side-effects. Promises aren’t invulnerable to the lure of the anti-pattern, so here are some to be aware of.

4.1 Deferring a Promise

A common mistake made when starting out using Promises is wrapping a Promise-aware procedure in a defer [7]. This happened to me when I was made fimilar with deferreds before I was aware of Promises (even though Deferreds inherit from Promises!). The largest issue created by this anti-pattern is that errors become lost, meaning your application could fail unexpectedly or the user is left waiting indefinitely for an action to complete that has actually errored. Another is that you are over-complicating your code, potentially confusing other developers.

Inadvertently deferring a Promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function promisifyString(str) {
return Promise.resolve(str);
}

function wrappedPromise() {
let defer = Q.defer();

promisifyString('Hello, World!').then(function(result) {
defer.resolve(result);
});

return defer.promise;
}

wrappedPromise().then(function(result) {
console.log(result); // => 'Hello, World!'
});

The above is exactly the same as doing:

Correctly using a Promise-aware function
1
2
3
promisifyString('Hello, World!').then(function(result) {
console.log(result); // => 'Hello, World!'
});

This might seem obvious, but that’s because I have defined the function that we are wrapping. If you don’t know that the function you are inadvertently wrapping returns a Promise then this anti-pattern might catch you out.

4.2 Nesting Promises

Promises are made to take us away from nesting, but you will often find Promises nested within each other callback-style. The problem here is that it means by using Promises we are no longer solving the fundamental architectural issue from which they are borne.

Nesting Promises
1
2
3
4
5
6
7
8
9
10
function makeHelloUniverse(str) {
str = str.replace(/World/, 'Universe');
return Promise.resolve(str);
}

promisifyString('Hello, World!').then(function(result) {
makeHelloUniverse(result).then(function(result) {
console.log(result); // => 'Hello, Universe!'
});
});

The correct way to approach this would be:

Avoiding nesting with Promises
1
2
3
4
5
promisifyString('Hello, World!')
.then(makeHelloUniverse) // pass the function in by reference
.then(function(result) {
console.log(result); // => 'Hello, Universe!'
});

4.3 The Non-Returning Promise

To chain Promises you must pass a result by returning something from a handler: a value or another Promise. This may not be an issue if a Promise does not actually pass on any data, but it may cause some confusion for developers (or you, a month down the line) who do intend to pick up the result of that Promise later on. This is demonstrated quite easily if we rewrite our makeHelloUniverse function.

A non-chainable Promise
1
2
3
4
5
6
7
8
9
function makeHelloUniverse(str) {
str = str.replace(/World/, 'Universe');
Promise.resolve(str); // NO RETURN STATEMENT
}

makeHelloUniverse('Hello, World!').then(function(result) {
// never invoked
console.log(result);
});


5. Apologies

Apologies to those who didn’t find an answer to their question here. Ask it in the comments and I will do my best to answer it. Or even better… ask StackOverflow [8]! If you’re starting out, lots of these examples will be confusing and may even make you feel like you’re taking a few steps back. Try using Promises ‘in the wild’, make mistakes, and you will soon start to understand them regardless of what you read on the web. Practically they appear difficult, but conceptually they are really quite simple.


6. References

[1] Callback Hell
[2] ECMAScript 2015: Promise specification
[3] ES6 compatibility table
[4] A comparison of JavaScript Promise libraries
[5] Bluebird Promise library
[6] Bluebird promisification documentation
[7] “Using Deferreds” from Q documentation
[8] StackOverflow ‘promise’ tag questions