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.
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.
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.