Understanding JavaScript Promises Part One: Promise Basics

OpenShift Ninja
Sep 23 · 4 min read
Image courtesy of Max Pixel

Today I will begin a series of articles on the topic of JavaScript promises. This is a complex topic, so I thought it best to break it up into several articles and not gloss over the important details. Promises are extremely useful, but it is important to understand how they work. For this first article, I’ll be covering the basics, namely what promises are used for.

Asynchronous Functions

When we develop web applications, we often have to deal with processes that operate independently of one another. A good example is a React UI making calls to a backend API to fetch data needed to render a component. If we were using a language such as Java, we could easily spin off threads to wait on those independent processes so that the main thread could continue.

JavaScript, however, is single-threaded. Because there is only one thread of execution on the page, stopping to wait for a response would also stop other activity on the page. Web applications need to be dynamic and responsive, so JavaScript provides a mechanism called asynchronous functions to allow developers to make requests and process the responses when they arrive.

Promises

Working with asynchronous functions is hard. They break up the usual flow of executing lines of code in order. Using callbacks can lead to confusing spaghetti code. Third parties created libraries that made working with asynchronous functions easier, and the approach they used was adopted into the JavaScript ES6 standard, which most browsers support (this guide has more details).

The standard defines objects called promises. A promise is a wrapper that captures the output of an asynchronous call. A promise has three main states: pending, rejected, or fulfilled. A promise remains in the pending state until it is either rejected or fulfilled. The rejected state is usually an error state, and the fulfilled state is usually the successful state.

A promise that is in the rejected or fulfilled state is also referred to as being resolved.

Resolve and Reject

So how does a promise go from pending to rejected or fulfilled? When you create a Promise, you give it a function that takes two arguments, usually named resolve and reject (these arguments can be named anything, but naming them this way makes their purpose clear):

These two arguments are callback functions that you call to indicate the promise is resolved. To put the promise into the fulfilled state, you call resolve, and to put the promise into the rejected state, you call reject.

It is possible to construct promises only using resolve or only using reject, but for the sake of clarity, I will use both in the signature in these articles.

The example above starts a long running process that can be run in the background. Once that process is complete, we fulfill the promise with the value true. If an error is thrown, we reject it with the error. Although I explicitly catch the error in the example, it isn’t required — an error thrown will automatically put the promise into the rejected state.

You can return a value from the body of the function passed to the promise constructor, but it will be ignored. Only the values given to resolve and reject (or a thrown Error) are preserved in the promise.

Using the Promise Value

Using the value from a promise is not as simple as looking inside the object you got back, as the value may not be there yet. To use the return value, you call methods on the promise, providing callbacks that will be called once the promise is resolved. The two main methods you will use are then and catch. The then method takes two arguments, a callback for when the promise resolves, and a callback for when the promise rejects:

Because providing both the resolve and reject callbacks to the then method can get messy, the catch method can be used to capture the reject value separately from the then method:

The difference is pretty minor, but when we talk about chaining, you will see that the catch method becomes especially useful.

Conclusion

In this article, I talked about asynchronous functions and how promises are used to capture the values of those functions. I talked about how promises are created, how they become fulfilled or rejected, and how you can use the values when the promises become resolved. There is still a lot more to talk about, so stay tuned for the next article on chaining.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade