The ES6 Promise

I promise you’ll get that data or an error eventually

Brian Engelhardt
8 min readFeb 22, 2017

One of my favorite new things in ES6 was the promise. A lot of other languages already had a similar concept. C#, Java, Ruby, Python all had a promise concept. Now Javascript is honestly a bit odd in that it would actually have benefited from promises long ago, but was pretty late to that party.

In C#, Java, Ruby and Python you have a couple of choices about how you handle operations that are inherently IO bound. The default option is to just pretend that they are synchronous. The system libraries will cause the thread to wait while the asynchronous operation runs. It then raises a signal to your thread and your application picks up exactly where it left off. This is all great and works really well in an environment with light weight threads like *nix. That is already more than about 60+% of developers who work with those languages understand about things like web requests, file access, and database access. In all of those languages, the default is to “block” on all IO. What does block mean. Well, I’ll tell you. If you don’t care, skip the next paragraph.

Application threads are a single continuous chain of execution. The OS schedules different threads to the cpu when they are needed, and forces them off when other threads need it, forcing threads to work together. Some OS vendors (Microsoft and Apple) do things to optimize “foreground” threads so that the entire OS feels mo re responsive than it may actually be. However, a large number of instructions that a thread does may need to wait for a response. To do this, the thread can actually “block.” That is, it actually sets its state to blocked so the OS knows not to schedule it, and then stops. The OS saves the thread’s state at the time, and turns the CPU over to other processes. However, right before it blocks, it triggers some action which starts in the CPU but leaves to other parts of the hardware. These actions, usually reads or writes to hardware resources other than ram (video, disk, network) run on separate controllers which receive instructions, do their thing, and then raise a signal. The blocked thread registers to be alerted on that signal. In short, it tells the OS “I have nothing to do, UNTIL that signal.” The OS then changes the state of the thread from blocked to signaled. The thread then picks up execution and either sets its state to running or goes back to blocked. If it sets its state to running, it reenters the scheduler and can continue executing as normal. The thread while in the blocked state was still “running” after a fashion. All of it’s data was still present, it still was in a predictable point in its execution path, but the point doesn’t progress until its ready.

All of the languages we’ve talked about so far have blocking I/O actions as the default behavior. However, most of the I/O libraries have non-blocking options as well. These options are usually named different to indicate their async nature, and return a promise. A promise is just an object which says that at some point in time, it will have a value. In Java and C# (and possibly the others) that promise has a function to block on it. This is used rarely but is done when certain really long requests are going to be needed further down the line. An optimization for response time that is relatively simple is to call the later, longer running requests early asynchronously. Then you simply go to the point in the code where you actually need the data and wait there for it. The requests have been running the whole time, while you were handling other things. This is where asynchronous programming becomes powerful.

Javascript is different. Not only is the asynchronous version the default, it really is the only option. Javascript runtimes are by necessity single threaded, but the majority of the browser runtimes share that thread with the render routines. This means that your javascript, as well as the layout and render routines all run in the same thread. It wouldn’t do in that case to have blocking requests really ever. Imagine if your browser stopped responding to any action while a fetch was happening for autocomplete. That would be miserable. So, instead, javascript has always depended on the event loop. Most languages can have an event loop, and it’s really common in UI code for anyone who has written high performance UI code in a desktop development environment. Event loops are kind of a different animal entirely because they are never blocking! So how does the event loop in javascript work? And what about keeping track of where you were?

To start with, how about the event loop. While I could write about this, and I do understand it very well, there isn’t much point when someone else has actually done this:

What about keeping track of where you are? With linear applications, you have an ongoing context in which you are executing. The variables you had before you blocked are still there. But in Javascript, you finish and return, so how do you get at those variables? That’s what closures are for:

Ok Brian, you are thinking, we have gotten a long way from promises. You are correct. Javascript and the event loop have always run on the callback concept. There’s a reason for that, but it’s mostly technical debt. So how does one use a promise in Javascript?

The promise object

If you are lucky enough to have a standard API call which already returns a promise, then all you have to know is how to use it. Promises have 3 states (hidden, but important to know) and two functions. The state can be ‘pending’, ‘resolved’, or ‘errored.’ Pending means that there will be a value later. Resolved and Errored mean that calling that promise’s retrieval functions may actually be synchronous. Of course, promises don’t show that state, and we should just generally assume that they are not synchronous.

As for functions, there are two, then and catch. Each of these are functions which you pass a callback into for handling the result of that promise. Functions passed into then and catch should each take one parameter, the result value in the case of then, and the error in the case of catch. That function can return a value as well. I’m going to get into how that works in just a moment.

By this point, I’m sure you are thinking that this is no different than callbacks, and so far, you are correct. All we’ve done so far is move the callback. So why are promises so valuable? Chaining. Promises have built in callback chaining, and even promise chaining. In a throw or catch callback, you have 3 options:

  • Return a value. This value will replace the original resolved value in chained promises.
  • Return a promise. This will cause chained promises to not resolve until that promise is resolved.
  • Throw an error. This will cause chained promises to “reject” (go to the error state) with that error.

You may have noticed I talked about all of those affecting chained promises. That is because then and catch take a callback and return a promise. That new promise has the following attributes:

  • Waits for the initial promise to resolve or reject.
  • Allows the callback to handle the result or error.
  • Waits for any promises returned from the callbacks.
  • Resolves to the value returned from the callbacks or the value a returned promise resolves to
  • Rejects any thrown error, or a rejection from a returned promise.

This means that you can have an api call that returns a promise, in the then function call a second api that returns a promise, using the result of the first one, and finally have another then function chained on that which handles the chained result. The actual code to consume that looks like this:

// fetch is a newer api that does http requests and returns a promise for them
fetch('someUrl')
.then(result => fetch(result))
.then(result => console.log(result))
.catch(error => console.error(error))

That code does two chained requests, using the result of the first one, and then console logs the result of the second request, or console errors any error encountered (with full stack trace by the way).

Equivalent code using Node.js request api:

request.get('url', (error, result) => {
if (error) {
console.error(error);
} else {
request.get(result, (error, result) => {
if (error) {
console.error(error);
} else {
console.log(result);
});
}
});

Note how the second version results in callback hell. Imagine having 4 or 5 nested behaviors (usually a result of a chain of things a user has to do). If you had to handle all of that through callbacks, you would end up with truly unreadable code. Meanwhile, the promise code is clean, and most importantly, linear. I can write it like I think about it, in a line.

One final note, it is possible to handle both result and error in one call on a promise. Then actually takes two callbacks. The first one is called if the promise resolves, the second one if it rejects. This can be handy for intercepting errors or successful values and triggering the next phase appropriately. Remember though that in your catch handler, if you don’t want future steps to continue, you need to rethrow the error. If a handler exists to handle the state the promise moves to and that handler doesn’t throw, then the promise resolves to the result of that handler. If none exist, then the value moves up the promise chain until there are no handlers left anywhere in the chain to deal with the state. That technically isn’t exactly how it works under the hood, but the behavior is accurate to the spec, and is sensible enough to work off.

Providing a promise

Of course, promises are only useful if the code you are using provides one. If your code doesn’t but has other ways of doing things, then we can easily wrap that code at the lowest level, and then work with promises all the way up. I’m going to show you a couple of examples, wrapping Nodes request for one example, and wrapping jQuery $.get for another.

function requestWithPromise(method, url) {
return new Promise((resolve, reject) => {
request.call(method, url, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}

As you can see, we instantiate a new promise. The promise constructor takes an execution function which it expects to have some asynchronous behavior. That function is passed two functions, a resolve function and a reject function. These cause the promise to move into the associated state, and take a single parameter, the value to resolve to or error to reject to.

You may be thinking, “but that looks just as messy as the callback option before.” Sure, but it is done once, and then anywhere you need to use it, you have promises, as opposed to having huge callback trees all over your code. Now for jQuery.

function get(url) {
return new Promise((resolve, reject) => {
$.get(url, {success: resolve, error: reject})
});
}

Wow, that was pretty succinct. That’s because jQuery uses separate callbacks for success vs error, so we can just hand the promise functions right off to it and be done with it.

Bonus: standard Node callback promise wrapper

Node.js callbacks all tend to follow a pretty standard format. They are passed two parameters, with the error parameter first. That means that you can actually get an easy wrapper pretty easy. Wrap async:

function wrapAsync(asyncFunc) {
return function wrappedFunc(...args) {
return new Promise((resolve, reject) => {
asyncFunc(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
});
});
}
}
let asyncWrappedRequest = wrapAsync(request.call);

Just like that, with a single place writing the wrapper code, you can wrap any asynchronous function which takes the callback as it’s final argument and passes parameters to the callback in the form error, result.

N.B. Some apis may return falsey values for error which still indicate an error. If you are wrapping any API which won’t necessarily return an actual error object, consider using if (error !== undefined) instead of just if (error).

This article is the second in what I hope will be a series on practical applications of lesser used ES6/ES7 features. All code samples can be found in this gist: https://gist.github.com/lassombra/38d9fb63eb8b973eda3b78612e758282

Articles in the series so far:

--

--