A Promise is a type of object built-into JavaScript which enables working with asynchronous events and reacting to them. A few common use cases for Promises includes:
- Making a network call, then taking some action when it is completed.
- Waiting for the user to interact with some input control.
- Wrapping one or more callbacks for improved code readability.
- Waiting for some predetermined period of time before proceeding.
- Loading external resources.
Promises are ubiquitious throughout JavaScript codebases, especially modern code, which also uses async/await, so understanding how Promises work is important.
Creating a Promise
To create a Promise from scratch, we can create a new instance of a Promise class. In the constructor, we pass in a function which has two parameters: resolve and reject, both of which are themselves functions:
- resolve: when the action taken in the function is completed, call resolve and pass in the value to be returned.
- reject: if we need to throw an error, we can pass the error into this function to indicate that an error has occurred.
In the code example below, we create a promise which resolves after a few seconds:
Generally, when developers first learn how to work with promises, they have trouble understanding the relationship between the function passed into the Promise constructor and the resolve + reject functions passed into that function. It is important to remember that resolve + reject are themselves functions to be executed. The bullet list below outlines the full sequence of events:
- A new Promise is created with a function passed into its first parameter, which we will call the "constructor function".
- The constructor function is invoked with two arguments: resolve and reject. Both of those arguments are themselves functions.
- The constructor function does what it needs to do, then will either end in a happy state or error state.
- If it ends in a "happy" state, it will call resolve and pass in the return value as an argument.
- If it ends in an error state, it will call reject and pass in the error value as an argument.
- If it does not call resolve or reject, the Promise will never resolve.
Reacting to a Promise
After a Promise resolves or rejects, we will want to do something with that value: update the UI for the user, log the value, store the value somewhere, change the value, etc. We can do this using methods which the Promise object provides:
- then: a method which will invoke a callback if the Promise resolves in a non-error state. The Promise's return value is passed into the callback as an argument.
- catch: a method which will invoke a callback if the Promise rejects. The error thrown is passed into the callback as an argument.
- finally: a method which will invoke a callback regardless of the Promise's state. This can be useful for de-allocating resources, logging, or any other action which needs to happen regardless if the promise succeeds or fails.
The code example below uses all three methods described above. Notice that we are chaining the calls. When we call then, catch, or finally, the method itself returns a new Promise which we can then add another then, catch, or finally. The value returned from the callback method will be the value which the new Promise object resolves to, or rejects to if an error is thrown.
A powerful feature of .catch: the callback can return a different value and the resulting promise will resolve to that other value. This allows us to gracefully handle errors. The code below catches a bad network call and returns an object of the same type the system expects.
It is also worth noting that callbacks can themselves return promises. If they do, the resulting Promise will resolve or reject when the Promise returned in the callback resolves or rejects, as demonstrated in the example below:
Async and Await
JavaScript provides a way of working with Promises in a way that resembles synchronous code. A function can use keywords async and await to await Promises and store their resolved values into variables directly. To use this:
- Mark a function as asynchronous: put the "async" keyword infront of the function declaration.
- Await a Promise: put the "await" keyword in front of a Promise to indicate that we will await its completion.
The code box below shows a function with async/await, then a similar piece of code without it. Note that we can use try/catch blocks with the await syntax.