In this article, we'll look at building a mental model for using promises to make your applications more efficient.
Promises are a way for the program to say:
I can't immediately get what you need right now, but I'll go and get it and let you know when I have it.
Here are some tasks that take a relatively long time to complete:
- Reading from disk
- Retrieving data over the network via HTTP, RPC, etc.
- Computationally expensive tasks like deserialization
Our programs could just say:
OK, I'll just wait and do nothing while you go and read from disk, retrieve data over the network, etc.
A promise allows your computer to work on other things while this operation is happening, freeing up compute resources on your machine.
When the promise is complete, your program will be informed that it can resume execution where it left off.
The easiest way to work with promises is with the
We'll use the free and open JSON Placeholder API for these examples. Feel free to try them out yourself.
By adding the
async keyword to our function
We won't have the data immediately because we need to go and fetch it from the REST API.
We use the
await keyword to signal that we will wait until the result
The mental model we can have here is that the program runs synchronously, meaning that one line executes after another in sequential order.
You don't need to worry about any of that though. All you need to know is
when you use the
await keyword in front of an asynchronous function such as
fetch, it is as if the code is running synchronously.
Now you've learned about the basics of promises. Let's look at how we can speed up your programs even further.
Let's create a new function that will pull the comment data in addition to the post data for a given post ID.
We'll use the familiar
await syntax from before so that we can
think about our program as if it were running synchronously.
Looks good, right? Almost.
The problem here is we are waiting for the post data to be fetched and deserialized before doing the same for the comments.
The issue is doing the work to fetch the comments doesn't depend on the results of fetching the post.
In other words, we can fetch the post and fetch the comments in parallel. And we can
do so with
Let's refactor our program. We'll split the fetching of the post and comments into two separate
functions. Then, we'll create a function that will let us fetch both in parallel using
Promise.all function accepts an array
n promises and executes them in parallel.
It returns an array
n results where each result
R[i] is the result of
p seconds to complete while
c seconds to complete.
Our previous implementation would take
p + c seconds to complete, while the new implementation
max(p, c) seconds to complete.
max(p, c) is equal to either
c, it follows that
max(p, c) < p + c. ◼️
The point to remember is this:
If 2 or more promises are mutually exclusive, then execute them in parallel with
In the examples we looked at previously, we made an assumption that everything would go well.
We'd go and fetch the data and everything would just work. But we can't rely on that all the time.
We want to handle the case when something goes wrong.
What if we pass in a
postIdthat does not exist on the server, and we get a 404 error?
We can guard against these cases using
The way you handle error cases will depend on the business rules of your app.
For demonstration purposes,
fetchPost to return
null and log a warning if the promise is rejected.
As an exercise, refactor
fetchComments to log a warning and return
 in the error case.
As a final note to reiterate, there's not a one-size-fits-all approach to error handling. You need to think about how you want to relay the fact there was an error to your customers.
This can be done in a number of ways, and should be done thoughtfully as dealing with software errors can be frustrating to customers.
Sometimes, you may want to kick off a process, but you don't necessarily need to block execution of the program while it's happening.
In this case you could consider omitting
await when you call the promise-returning function.
For example, let's pretend we're creating a
createComment(postId, message) function. We won't concern
ourselves with the all the details of the implementation. But let's say we want to fire off a push notification
to the author of the post we're commenting on.
We'll do so with a
sendCommentNotification(postId, message) function. This function would run asynchronously to
send the push notification.
We don't need the result of
sendCommentNotification, so there's no need for us to
await the result.
catch the error case and log an error so that we have some visibility into any issues
that may occur.
We can do this by chaining
catch onto the promise itself.
We can kick off the process of sending the push notification without blocking the thread.
createComment can return
true while sending of the notification is still ongoing.
If we were to have used
await in front of
sendCommentNotification, we would have had to have
waited for that promise to be resolved or rejected before
createComment could return
Promises are a powerful concept that are used all over the place in modern applications.
Hopefully this article helped you understand how they work and how you can leverage them to make your apps more efficient.
Recapping everything we touched on in this article:
awaitsyntax to program asynchronous code as if it were synchronous.
Promise.allto execute mutually exclusive asynchronous functions in parallel.
catchto handle error cases gracefully.
- Consider not using
awaitif you don't need the result of the promise.