- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Using Promises with JavaScript
In this hands-on lab, you'll learn how to create and consume promises, which are a way to eliminate passing around callbacks and write code that is able to express what is happening behind the scenes. You'll learn about the three states of each promise: pending, fulfilled (or resolved), and rejected. You'll also create a promise to replace the callback for an HTTP request. By the end of the lab, you'll also use axios, a promise-based HTTP client for JavaScript, to consume a promise.
Lab Info
Table of Contents
-
Challenge
Introduction
Promises are a way to eliminate passing around callbacks and write code that is able to express what is happening behind the scenes.
Every promise has one of 3 states:
- pending - This is the initial state
- fulfilled or resolved - The operation completed successfully
- rejected - The operation failed
HTTP calls are great opportunities to use promises. While the request is executing, the promise will be in the
pendingstate. If the request succeeds (i.e. sends back a 200 code), then the promise will befulfilled. If the request fails (e.g. a 404 or 500 error), then the promise will berejected.In this course, you'll start by creating a promise that we'll use to replace the callback for an HTTP request.
Once you've learned how to create promises, you'll move on to consuming them. You'll do that by using axios, which is a
promisebased HTTP client for JavaScript that follows the same rules as above. That is, when an HTTP call is successful,axioswillresolvethe promise. When an HTTP call is unsuccessful, for example, when it returns a 404,axioswillrejectthe promise.And because each
axiosfunction returns a promise, you are able to chain multiple calls together.We'll cover each of these examples, and more, in the following steps.
-
Challenge
Create and resolve a promise
Open the
created.promises.jsfile, which we'll use for the first two steps. Notice thecustomPromisefunction. It uses the node module,httpto perform agetrequest to get anorderStatusby its id.This code is asynchronous. That is,
http.getwill not return the data, instead uses a callback (thecbvariable) to send either a successful messageres.on('data', (chunk) => { cb(JSON.parse(chunk)); });Or handle the case where the call is not successful on line 11.
cb(res.statusCode);Instead of using a callback, we'd like this function to use a
promiseso that the consumers don't have to pass around a callback.Promises are plain JavaScript objects. As a result, the first step in using a promise is creating a new promise. The constructor takes a function, and the first parameter of that function is another function most commonly named
resolvenew Promise((resolve) => {}) ``` Now that you've created a promise, it's time to tell the promise to resolve. Use the `resolve` function that is passed in to the constructor. -
Challenge
Create and reject a promise
Just like a promise can be
resolved, it can also berejected. This is how you will communicate to your consumers that the promise failed.As you've already seen, when you create a promise, you pass in a function that takes a parameter commonly named
resolvenew Promise((resolve) => {});The function can also take a second parameter commonly called
reject.new Promise((resolve, reject) => {}) -
Challenge
Handle a resolved promise
Now that you've created a promise and been responsible for either resolving or rejecting it, it's time to move on to the more common use case: consuming promises.
To start, navigate to the file
http.promises.js. We'll use this file for the next several steps.Start with the function
getOrders. This function usesaxiosget a list of available orders in our e-commerce system.Since
axios.getreturns a promise, the code on line 6 is incomplete. This linereturn axios.get('http://localhost:8888/orders');does not return the orders, but rather a promise that will eventually resolve with orders.
To have
getOrdersactually return the orders, we need to handle the case where the promiseresolves.When a promise
resolvesit calls a function namedthen, which itself takes a function. For example:.then(result => { });One thing to know about
axiosis that the response that it returns contains more than just the data. It also contains meta-data such as thestatus code,url, and more. It stores the data in a property nameddatain that response. -
Challenge
Chain resolved promises
Promises can be chained together, this means that you can have successive
thenfunctions.That can be helpful, particularly when you need to make one call before you can make another call. For example, in our e-commerce application, we might fetch a specific order and then with that information, look up an additional piece of data.
There's not really anything special with the syntax of chaining
thenfunctions. They'll look something like:.then(response => {}) .then(response => {})There's also not really a limit on the number of promises you can chain together!
-
Challenge
Handle a rejected promise
So far you've seen how promises work when everything goes right, but what about when there's an error?
With HTTP requests, like we're doing with
axios, an error could be anInternal Server Error(a 500 code), or even something like the404 - Not Foundstatus. It's important to know if your HTTP call succeeded.Promises help with this as well. Just like the
thenfunction, promises offer a second function,catchthat will handle errors. Syntactically, it's very similar.catch(err => {});To test this out, make a call to get an
orderwith an id of -1. That order doesn't exist, and so the server will return 404, and theaxiospromise will fail. -
Challenge
Execute code after a promise is settled
Oftentimes you will want to perform some kind of action after your promise runs regardless of if it succeeded or failed. For example, after making an HTTP call, you might want to turn off a "loading indicator" so that the user can continue using the application.
With promises, this state is called
settled. That is, the promise has executed and has either beenfulfilledorrejectedand so the promise is now in thesettledstate.You've already seen that promises use a
.thenfunction for thefulfilledresult and a.catchfor therejectedresult. So it shouldn't be too surprising that there's a third function for thissettledstate. That function is namedfinallyand it is used very similar to the other functions..finally(() => {}); -
Challenge
Wait for all calls to succeed
Up until now you've been working with a single promise at a time, and that will be helpful in handling asynchronous code. However, one aspect of promises that are very powerful is the ability to queue up multiple promises so that they can run in parallel.
Open up the
multiple.promises.jsfile.Look at the function
loadPartialMetadata(), and you can see three promises assigned to three variables on lines 4-6let categories = axios.get("http://localhost:8888/itemCategories"); let statuses = axios.get("http://localhost:8888/orderStatuses"); let userTypes = axios.get("http://localhost:8888/userTypes");Remember that
axiosreturns promises. Socategories,statusesanduserTypesare not the data from the API, but rather promises that point to that call.Each of these API calls return metadata that your users will use throughout the app. There's no business case for why one of those API calls should wait on the others. That is
itemCategoriesdoesn't depend onorderStatuses.At the same time, it doesn't make sense to allow the program to continue until all of the calls have returned their data.
The
Promiseobject accounts for this with a variety of functions that can be used for queuing up promises.The first one to look at is
Promise.all. This function accepts a list of promises and then willresolvewhen all promisesresolve.Promise.all([promise1, promise2, ... promiseN]) ``` Since `Promise.all` creates a new promise, it will now behave the same as the other promises you've worked with. That means that when it `resolves` it will still call a `.then` function. One important piece to know is what the response data will be inside your `.then` function. `Promise.all` resolves with an array of data that is in the same order as the array it was called with. That is, in this code block: ```JavaScript Promise.all([promise1, promise2]) .then(result => {})The variable
resultwill be an array[response1, response2]The
loadPartialMetadataneeds to make a make sure that it loadsitemCategories,orderStatuses, anduserTypes. However, for performance reasons, it does not want to do so sequentially. It's important to note one thing about the behavior of.all. It willresolvewhen all of the promises have resolved. However, it will fail the instant that any one of the promises isrejected.For example, if
categoriessucceeded, andorderStatusesfailed,Promise.allwould not wait around to see whatuserTypeswould do, it would immediately reject the promise and your.thenblock would not be called. You would need to handle that case by chaining a.catchfunction.However, in the next step, you'll learn about another function on the Promise object that can handle this case.
-
Challenge
Wait for all calls to settle
Inside the
multiple.promises.jsfile there's a second function namedloadAllMetadata. This function looks very similar to theloadPartialMetadatafunction from the last step.However, there is one difference, and that is, there's a new metadata type,
addressTypes.In the API,
addressTypesdo not exist. So if you tried to load that metadata you would get a404 - Not Foundresponse from the API.If you were to use
Promise.all, like in the last step, you would never be able to load your metadata, becauseaddressTypeswould fail. While that might be useful in some cases, it's not always useful. Sometimes you would like to have whatever data you can, and move on, even if all the promises don't resolve.For that, there's the function
allSettled. Likeall, this function creates a newpromise. This newpromisewill onlyresolveonce all the promises that are passed in aresettled(eitherresolvedorrejected).The function is called in the same way as
all, by passing in a list of promises.The data that is returned is slightly different, however. Like
allthe data will be returned in the same order it was called. Unlikeall, though, each returned value will have astatusproperty that will be eitherfulfilledorrejectedThe
loadAllMetadatafunction needs to handle the case whereaddressTypesfails whileitemCategoriesandorderStatusessucceeds. -
Challenge
Putting It All Together
Now it's time to take what you've learned in the previous steps and apply it to an application.
Start by clicking the
Runbutton in the bottom right.Then, in the
Web Browsertab, refresh the browser and you'll see a store front for Carved Rock Fitness.Notice that there is a
My Ordersmenu item. This item will show you the orders that you have in the system. But in order to get the orders to show up, you need to fetch some data.Start by opening the file
store/modules/orders.This contains a function where you'll be writing your code.
You'll notice at the top of the function, there are promises for every piece of data that you will need.
let statuses = axios.get("http://localhost:8888/orderStatuses"); let users = axios.get("http://localhost:8888/users"); let items = axios.get("http://localhost:8888/items"); let addresses = axios.get("http://localhost:8888/addresses"); let orders = axios.get('http://localhost:8888/orders?userId=2');Notice that the call to
ordersspecifies theuserId. It's hard-coded to 2 for this example, but in a real world application, it would use the id of whatever user is logged in.It's important to note that this is not a good way to load data in a real application. It works in this situation because there is not much data and so connecting the data on the client will not have a performance impact. In a more real-world application, a request to
orderswould likely return the status, items and addresses as one object.To make the orders show up the following steps need to happen:
- Queue up all promises using
Promise.all - Set the metadata, such as
statuseson themetadataobject. - Create an array of orders that contains the
status,address, and list ofitemsAt this point, you've retrieved all of the metadata that you will need for the order, and you've passed theorderdata on to the nextthenblock. In theWeb Browsertab, if you refresh and click theMy Ordersmenu item, you should 2 cards, one for each order.
- Queue up all promises using
About the author
Real skill practice before real-world application
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.