Promises are a popular subject among JavaScript developers, and it’s essential to familiarize yourself with them. They can be challenging to grasp at first, requiring several tutorials, examples, and considerable practice to fully understand.
This tutorial aims to simplify JavaScript Promises and encourage their practical use. We’ll explore their definition, purpose, and mechanics. Each step is paired with a jsbin code example for hands-on learning and further experimentation.

What constitutes a JavaScript promise?
A promise is a method that will eventually yield a value. Think of it as the asynchronous counterpart to a getter function. Its core concept can be summarized as:
| |
Promises offer an alternative to asynchronous callbacks with several advantages. They’re gaining traction as libraries and frameworks increasingly adopt them for asynchronous operations. Ember.js is a prime example of such a framework.
Various several libraries implement the the Promises/A+ specification. We’ll cover the fundamental terminology and illustrate the underlying concepts through practical JavaScript promises examples. The code demonstrations will utilize rsvp.js, a widely used implementation library.
Prepare yourself, we’ll be rolling plenty of dice!
Acquiring the rsvp.js library
Both server-side and client-side applications can utilize Promises and, consequently, rsvp.js. To integrate it with nodejs, navigate to your project directory and execute:
| |
If you’re working on the front-end and employing bower, it’s merely a
| |
away.
Alternatively, for immediate use, include it directly through a script tag (easily accomplished in jsbin via the “Add library” dropdown):
| |
Analyzing the properties of a promise
A promise can exist in one of three states: pending, fulfilled, or rejected. Upon creation, it resides in the pending state, from which it can transition to either fulfilled or rejected. This transition is termed as the resolution of the promise. The resolved state marks the promise’s final state, remaining constant once achieved.
In rsvp.js, promises are generated using a mechanism known as a revealing constructor. This constructor accepts a single function as a parameter and immediately invokes it with two arguments, fulfill and reject, responsible for transitioning the promise to the fulfilled or rejected state, respectively:
| |
This JavaScript promises pattern, termed a revealing constructor, grants the constructor function visibility into the single function argument’s capabilities while preventing external entities from manipulating the promise’s state.
Promise consumers can respond to state changes by registering handlers using the then method. It accepts both a fulfillment and a rejection handler function, either of which can be omitted.
| |
The execution of either the onFulfilled or onRejected handler occurs asynchronously, contingent on the outcome of the promise’s resolution process.
Let’s examine an example to illustrate the execution order:
| |
This snippet generates output akin to the following:
| |
However, if luck prevails, we might observe:
| |
This promises tutorial highlights two key points.
Firstly, the handlers attached to the promise are indeed executed asynchronously, following the completion of all preceding code.
Secondly, the fulfillment handler is invoked solely when the promise is fulfilled, receiving the resolution value (in this scenario, the dice roll result). The same principle applies to the rejection handler.
Chaining promises and the trickle-down effect
The specification mandates that the then function (handlers) must return a promise themselves. This enables promise chaining, yielding code that resembles synchronous execution:
| |
In this example, signupPayingUser returns a promise, and each function within the chain is invoked with the return value of its predecessor upon completion. Effectively, this serializes calls without obstructing the main execution thread.
To demonstrate how each promise resolves with the return value of the preceding element in the chain, we return to our dice-tossing scenario. Our objective is to roll the dice a maximum of three times, or until the first six appears jsbin:
| |
Upon executing this promises example, you’ll encounter output similar to this on the console:
| |
The promise returned by tossASix is rejected unless a six is rolled, triggering the rejection handler with the actual roll value. logAndTossAgain displays this result on the console and returns a promise for another dice roll. This subsequent roll, if unsuccessful, is also rejected and logged by the next logAndTossAgain.
Occasionally, fortune might favor you*, yielding a six:
| |
* Achieving this doesn’t necessitate exceptional luck; the probability of rolling at least one six within three attempts is roughly 42%.
This example reveals an additional insight. Notice how subsequent to the first successful six, no further rolls occur. Observe that all fulfillment handlers (the first arguments of then calls) in the chain are null except for the final one, logSuccess. According to the specification, if a handler (fulfillment or rejection) is not a function, the returned promise must resolve (fulfilled or rejected) with the same value. In this instance, the fulfillment handler, being null and therefore not a function, results in the promise being fulfilled with a value of 6. Consequently, the subsequent promise returned by the then call (the next in the chain) is also fulfilled with 6.
This pattern persists until an actual fulfillment handler (a function) is encountered. In our case, this occurs at the chain’s end, where the result is printed to the console.
Error management
The Promises/A+ specification dictates that promise rejections or errors thrown within a rejection handler should be intercepted by a downstream rejection handler.
The trickle-down technique offers a clean approach to error handling:
| |
By positioning a rejection handler at the very end of the chain, any rejection or error within a fulfillment handler will propagate downwards until it reaches displayAndSendErrorReport.
Let’s revisit our dice example and observe this in action. Suppose we aim to asynchronously roll dice and log the results:
| |
Executing this code yields no apparent output, neither console messages nor errors.
However, an error is indeed thrown behind the scenes. Its absence stems from the lack of rejection handlers in the chain. Since handler code executes asynchronously on a fresh stack, it doesn’t even reach the console log. Let’s rectify this fix this:
| |
Now, running the code exposes the error:
| |
Our oversight was neglecting to return a value from logAndTossAgain, causing the second promise to be fulfilled with undefined. Subsequently, the next fulfillment handler attempts to invoke toUpperCase on this undefined value, resulting in an error. This underscores another crucial point: always ensure a return value from handlers, or be prepared to handle the absence of a value in subsequent handlers.
Constructing higher-level promises
Having covered the basics of JavaScript promises, we can now leverage their composability to create “compound” promises tailored to specific behaviors. The rsvp.js library provides several pre-built options, and you’re always welcome to craft your own using the primitives and these higher-level constructs.
Our final, more intricate example ventures into the realm of AD&D role-playing, utilizing dice rolls to determine character scores. Such scores are typically derived from rolling three dice for each skill of the character.
Let’s examine the code first and then delve into the novel aspects:
| |
The toss function should be familiar from our previous example. It generates a promise that consistently resolves with the result of a dice roll. Here, we’ve employed RSVP.resolve, a convenient method for creating such promises with minimal overhead (see [1] in the code).
Within threeDice, we create three promises, each representing a dice roll, and combine them using RSVP.all. This function accepts an array of promises and resolves with an array of their respective resolved values, preserving their order. Consequently, we obtain the roll results in results (see [2]) and return a promise fulfilled with their sum (see [3]).
Resolving the final promise then logs the total:
| |
Applying promises to real-world scenarios
The applications of JavaScript promises extend far beyond gratuitously asynchronous dice rolls.
Consider replacing the three dice rolls with three AJAX requests to distinct endpoints, proceeding only upon successful completion of all requests (or handling any failures). This illustrates a practical use case for promises and RSVP.all.
Properly implemented promises result in readable code that’s easier to understand and debug compared to callbacks. They eliminate the need for conventions like error handling, as these aspects are inherently addressed by the specification.
This JavaScript tutorial merely scratches the surface of what promises can achieve. Promise libraries offer a wealth of methods and low-level constructors at your disposal. Master these tools, and unlock boundless possibilities.
Meet the author
Balint Erdi, once an avid role-playing and AD&D enthusiast, now channels his passion into promises and Ember.js. His enduring love for rock & roll inspired him to create a book on Ember.js, an application drawing upon a rock & roll theme. Stay tuned for its launch Sign up here.