Skip to content

Latest commit

 

History

History
144 lines (100 loc) · 7.98 KB

37 - Working with Multiple Promises.md

File metadata and controls

144 lines (100 loc) · 7.98 KB

The last example we 'stepped', which is to say that we waited for the post to come back before we actually found the author because we needed to know who the author of the post was before we could hydrate the author. The first thing needed to happen before the second thing could happen. It's sort of a waterfall approach.

In some cases, you just want to fire them all off at the exact same time, because they're not dependent on each other, and you want to get two things back, or four things back, as soon as possible.

I've got an example for weather, where I'm going to fetch the weather, and it's going to come back after two seconds, 2000 milliseconds, with a temperature of 29, Sunny With Clouds.

Then I also want to go get my tweets. I'm not searching for tweets based on weather or anything like that. The tweets have nothing to do with the weather, other than I just need to get these two pieces of data. This comes back after 500 milliseconds, half a second.

const weather = new Promise((resolve) => {
    setTimeout(() => {
        resolve({temp: 29, conditions: 'Sunny with Clouds'});
    }, 2000);
});

const tweets = new Promise((resolve) => {
    setTimeout(() => {
        resolve(['I like cake', 'BBQ is good too!']);
    }, 500);
});

The way that we can do that, instead of chaining .thens together, we can say Promise.all, and you pass it an array of promises, so in this case, weather and tweets, and you call .then against that. Then when that comes back, we are going to get our responses.

const weather = new Promise((resolve) => {
    setTimeout(() => {
        resolve({temp: 29, conditions: 'Sunny with Clouds'});
    }, 2000);
});

const tweets = new Promise((resolve) => {
    setTimeout(() => {
        resolve(['I like cake', 'BBQ is good too!']);
    }, 500);
});

Promise
    // formatting .all and .then on separate lines helps make your code readable
    .all([weather, tweets])
    .then(responses => {
        console.log(responses);
    });

[2:04]

In the console, you should notice that it takes two seconds to come back. This is because every Promise in the function has to finish before we can run the .then to get a result. In this case, our longest promise takes two seconds. So for example, if a promise takes takes 10 seconds, your Promise.all is going to take 10 seconds to resolve. If one takes 15 seconds, it's going to take 15 seconds. The slowest response decides how long these things should actually take.

In our case, the console should return an array of [Object, Array[2]], where the first item in the array is our weather object, "Sunny with Clouds" and temp: 29, and our second item is our array of tweets.

If we wanted to clean things up a bit, we could also do something like this:

Promise
    // formatting .all and .then on separate lines helps make your code readable
    .all([weather, tweets])
    .then(responses => {
        const [weather, tweets] = responses;
        console.log(weather, tweets)
    });

Now, we have two separate variables, one with our weather, one with our tweets in it, and we can go ahead and start populating the data on our actual home page.

However, it's probably not a good idea to name this weather and tweets. Why? Because our promises are named weather and tweets, so maybe call it weatherInfo and tweetsInfo.

Let's actually do some with some real data here.

We need two APIs. If you work with an API during the day, I encourage you to go grab that API. Otherwise, you can use the ones I've got right here:

const postsPromise = fetch('http://wesbos.com/wp-json/wp/v2/posts');
const streetCarsPromise = fetch('http://data.ratp.fr/api/datasets/1.0/search/?q=paris');

I've got postPromise here, which is going to go to my blog and grab all of my latest posts, which I've used before. We also have the streetCarsPromise, which is going to go and fetch some data from the Paris transit system.

We need to resolve each promise, or rather, they will resolve themselves, but we need to listen to when they are both resolved.

const postsPromise = fetch('http://wesbos.com/wp-json/wp/v2/posts');
const streetCarsPromise = fetch('http://data.ratp.fr/api/datasets/1.0/search/?q=paris');

Promise.all([postsPromise, streetCarsPromise])
      .then(responses =>  {
          console.log(responses);
      })

Before we go to run this, you actually can't run this locally on your file system. So if we want to be working with fetch API, you need to be running it through some sort of server.

I actually have a browser-sync command I use to do this from the directory I've got this file saved in:

browser-sync start --directory --server --files "*.js, *.html, *.css

You can get browser-sync if you don't already have a way of spinning up a server, and you can get it by typing npm install -g browser-sync in your terminal to install it as a global package.

So once you get that all going, let's go ahead and take a look at our console for responses, which we'll find is an array of two things.

We got the first response that comes back from Wesbos.com, and we got the second response that comes back from the Paris transit system but we don't see the data in our body... we actually have a problem where we have to convert a readable stream into json, right? This is the problem we had on the first example.

Here's how to do that with two things:

const postsPromise = fetch('http://wesbos.com/wp-json/wp/v2/posts');
const streetCarsPromise = fetch('http://data.ratp.fr/api/datasets/1.0/search/?q=paris');

Promise.all([postsPromise, streetCarsPromise])
      .then(responses =>  {
          return Promise.all(responses.map(res => res.json()))
      })

What we can do is we can return a promise.all again, and then we will take each of these things, which is the responses, and we can just map over them and call .json on each one.

We'll say response, and we will return response.json. Why do we have to call this res.json? Why can't we just say json.parse around, res.body or something like that?

We actually use res instead of responses here, because there are many different types of data that could come back. If you check out the documentation on MDN, you can see that the data can come back as an .arrayBuffer(), .blob(), .json(), .text(), or .formData().

Don't just assume that your APIs or your AJAX requests are always going to be json, because it could be any of those data types.

What are we doing? Our response is, in this case, in an array, and .map takes every item out of an array, does something to it, and then returns a new array.

What we're doing here, is we're taking the array of responses and taking each one and calling .json() on it. This returns a second promise, which we can call .then on, and that should then, responses, that should give us some real data:

const postsPromise = fetch('http://wesbos.com/wp-json/wp/v2/posts');
const streetCarsPromise = fetch('http://data.ratp.fr/api/datasets/1.0/search/?q=paris');

Promise.all([postsPromise, streetCarsPromise])
      .then(responses =>  {
          return Promise.all(responses.map(res => res.json()))
      })
      .then(responses => {
          console.log(responses);
      })

So if we open this up, we'll see our API data in an array. In this case, we got about 10 posts, and an object containing some information about the Paris transit system.

So what we've done overall here is using a Promise.all on our initial promises, postsPromise and streetCarsPromise. Then when both of those promises come back, we run .json() on all of them. Then, when both of those come back from being turned from just regular data into json, which is instant, then this final promise then is called and we can do whatever it is that we want with that data.