Promises Practice

Learning Goals

  • Review these concepts:
    • single-threaded JS
    • asynchronous JS
    • event loop
  • Utilize Postman as a tool for making requests to APIs

  • Utilize fetch to get data from an API and utilize the data in a FE app

Warm Up

In your journal, write your answers to the following questions. It is totally OK (and encouraged!) to look back at the notes you took yesterday!

  • What is the difference between synchronous and asynchronous JS?
  • What are the different statuses of a Promise?
  • What is returned when we use the fetch API?

What We Will Be Building

spec

— –>

Promises Practice Repo

We’re going to be building out this site with two different repos. One for the client side code and another is an API that serves up a collection of members. Something to note is that the API given to us doesn’t automatically give us all of the info needed to display the staff members. There is a second nested endpoint we will need to fetch… More on this in a moment.

Clone this repo and the promises-api repo down.

Have these directories open in two separate terminal tabs so you can see them both at the same time. For both, run:

npm install

npm start

Postman

Postman is a great tool for making requests to an API and seeing the response you get back in a quick, easy-to-use interface! We will be utilizing this tool throughout Mod 3 and beyond!

Download the app now and get it running now!

Getting Started

Since we are going to be getting our data from an API, it’s usually a good idea to get a sense of what our responses look like when we make a request to a certain endpoint!

You Do

  • Make sure you have the Promises-API running on port 3001
  • With a partner, open Postman and make a GET request to the following endpoint: http://localhost:3001/api/frontend-staff
  • What is included in the response?
  • Try entering one of the endpoints found in the info key for one of the objects. What is included in the response?

As you may be starting to see, there is a lot of data available to us but it is going to be a little tricky to access this data. We are going to work together to break down how to access this data to get our app running.

Step 1 - Getting the Initial Bio Info

Before we can get any of the detailed info for each staff member, we have to start at the initial endpoint as our “entry” into the rest of the dataset. We will be utilizing the fetch API to access this data!

If you’re not feeling totally comfortable with fetch yet, I suggest taking a five minutes to review the docs.

Fetch Details

  • Fetch returns a Promise, which will either resolve or reject depending on the status of the promise.
  • You might want to take a look at when fetch actually catches errors here. The API can actually be set up in a way that can help fix this, but this is a major reason why some people dislike fetch.
  • Since fetch returns a promise, it makes sense that you can chain .then() or .catch() to it.

You Do

Take 5 minutes to read this article about where we should kick off these type of network requests within a React app. Be prepared to discuss the best approach for this problem!

How/Where Will We Fetch?

As you read, utilizing the componentDidMount() method is the best place to kick off our network request to the first endpoint!

OK, so let’s start by making our initial fetch request and seeing what we get back. See if you can…

  • Use fetch within componentDidMount() to make a request to http://localhost:3001/api/frontend-staff
  • console.log the data that comes back!

Initial Fetch w/ console.log Solution

componentDidMount() {
  fetch('http://localhost:3001/api/frontend-staff')
    //fetch returns another Promise, which resolves to a Response object
    //We can call .json() on the Response object to access the data from
    //the body of the Response object
    //.json() returns another Promise, which is why we need to chain
    //a .then to allow it to resolve!
    .then(response => response.json())
    .then(staffData => {
      //let's check what the data even looks like...
      console.log('all data', staffData);
      console.log('bio data', staffData.bio);
    })
}

Step 2 - Accessing the bio Data

OK, so it looks like we want to dig into the .bio from the data that gets returned after we run the .json() method on our initial response object! But, now we have multiple API endpoints that house the data we are really after… woof.

With a Partner

  • Discuss a way we could access the data of each member of the staff
  • Since we will have to utilize fetch for each staff member’s info, what will be returned from these fetch requests?
  • Write some pseudocode for how you could approach this problem!

Possible Pseudocode Pt. I

  1. Make initial fetch to http://localhost:3001/api/frontend-staff in componentDidMount
  2. Iterate using .map() over the array of staff members
    • fetch from each endpoint within the .map
    • Since this .map is making network requests using fetch, the map will return an array of Promises
  3. Find some way to resolve all of these daggum Promises from the .map!

Great! We have a plan! Plans are #tite. Let’s work on getting the first two steps implemented.

Iterating Over Staff Members Solution Pt. I

componentDidMount() {
  fetch('http://localhost:3001/api/frontend-staff')
    .then(response => response.json())
    .then(staffData => {
      //let's just see what we are getting back!
        const promises = staffData.bio.map(staffMember => {
          return fetch(staffMember.info)
            .then(res => res.json())
        })
      console.log('promises', promises);
    })
}

This is what the data looks like in our console:

console.log of Promises

Alrighty then… We are getting a little bit closer to grabbing all of the data we need. But we still have a little work to do.

Step 3 - Getting the Data We Need

So we have access to two new pieces of data from this fetch - the bio and image. But we still need to have all of the relevant info for each staff member, especially their name!

You Do

With a partner, see if you can find a way to extract the necessary data from the response from our fetch of each staff member and find a way to combine it with their name!

Combining Data w/in .map() Solution

componentDidMount() {
  fetch('http://localhost:3001/api/frontend-staff')
    .then(response => response.json())
    .then(staffData => {
        const promises = staffData.bio.map(staffMember => {
          return fetch(staffMember.info)
            .then(res => response.json())
            //after we have pulled the data off the response...
            .then(info => {
              //create an object with the name
              return {
                name: staffMember.name,
                //and the remaining info (bio + image)
                ...info
                //bio: info.bio,
                //image: info.image
              }
            })
        })
      console.log('promises', promises);
    })
}

This is what our console.log looks like: console.log of Promises pt. 2

Hell yeah! We now have the data looking how we want. But the problem is, it’s still a Promise and we can’t work with it yet. Consarnit! And worst of all, now we have this giant array of Promises - how on earth are we going to resolve all of them?!

Research Spike

  • The Problem: we have multiple Promises that need to get resolved.
  • Spend 5 minutes researching/figuring out how we can resolve multiple Promise objects!

Step 4 - Resolving ALL of the Promises!

Solution to Problem from Research Spike!

Promise.all() has entered the chat!

  • Promise.all() takes an array of promises and returns a single promise that will either resolve when every promise has resolved or reject with the reason of the first value in the array that reject.
  • If the promise array resolves completely, the resulting values will be an array of values whose results are ordered by the order of the promises in the original array - regardless of which promises resolve first!
  • This allows us to make ensure that all of the data is returned at one time, in the correct order, rather than making several different fetch calls which could resolve at different times and hoping for the best!

Dope! So now we have a way to work with all of the Promises returned from our .map! But before we implement it, let’s check the docs and make sure we understand what we are getting back from this solution!

You Do

  • With a partner, see if you can return all of the Promises into a format we can work with by console.log the data! Hint: What does Promise.all return?
  • After you can see the data, how could we store the data within the state of our App?

Promise.all() Solution

componentDidMount() {
  fetch('http://localhost:3001/api/frontend-staff')
    .then(response => response.json())
    .then(staffData => {
        const promises = staffData.bio.map(staffMember => {
          return fetch(staffMember.info)
            .then(res => res.json())
            .then(info => {
              return {
                name: staffMember.name,
                ...info
              }
            })
        })
        //this will return us a single Promise!
        //If it returns a Promise, we need to use .then
        //to give it time to resolve!
        return Promise.all(promises)
    })
    //We can now set the workable data to our state!
    .then(staff => this.setState({ staff }))
}

Step 5 - Building a Helpers File

Bravo! - we have successfully integrated the nested data from the API into our App’s state! But, this is a pretty burly chunk of code and isn’t super readable. This is where you might find it more beneficial to break this into a separate file and bring in the functionality only when you need it!

You Do

With a partner, see if you can extract the logic from our fetch call into it’s own function within a new file!

  1. Create a new file called helpers.js
  2. Create a function called fetchStaffBios
  3. Think about what logic from the fetch from our App needs to be broken out and extract that logic into the fetchStaffBios function
  4. Import the function into your <App /> component and use it within the fetch of componentDidMount. If the app still works, you did it!
  5. Add some error handling to your fetch to handle if something goes awry anywhere along the way!

Final Solution!

// helpers.js
export const fetchStaffBios = staffData => {
  const promises = staffData.bio.map(staffMember => {
    return fetch(staffMember.info)
      .then(res => res.json())
      .then(info => {
        return {
          name: staffMember.name,
          ...info
        }
      })
  })
  return Promise.all(promises)
}

// App,js
class App extends Component {
  constructor() {
    super();
    this.state = {
      staff: [],
      error: ''
    };
  }

  componentDidMount() {
    fetch('http://localhost:3001/api/frontend-staff')
      .then(response => response.json())
      .then(staffData => fetchStaffBios(staffData))
      .then(staff => this.setState({ staff }))
      .catch(error => this.setState({ error }))
  }

  render() {
    //JSX
  }
}

Resources

Lesson Search Results

Showing top 10 results