-
Notifications
You must be signed in to change notification settings - Fork 169
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17 from mbuttler/nov-5
36, 37, 39
- Loading branch information
Showing
3 changed files
with
438 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
Another useful case for `Promise` is when you need some sort of flow control. This is an example where you would probably find out on a back end and maybe something like Node.js, when you are querying a database. | ||
|
||
```js | ||
const posts = [ | ||
{ title: 'I love JavaScript', author: 'Wes Bos', id: 1 }, | ||
{ title: 'CSS!', author: 'Chris Coyier', id: 2 }, | ||
{ title: 'Dev tools tricks', author: 'Addy Osmani', id: 3 }, | ||
]; | ||
|
||
const authors = [ | ||
{name: 'Wes Bos', twitter: '@wesbos', bio: 'Canadian Developer'}, | ||
{name: 'Chris Coyier', twitter: '@chriscoyier', bio: 'CSS Tricks and CodePen'}, | ||
{name: 'Addy Osmani', twitter: '@addyosmani', bio: 'Googler'}, | ||
]; | ||
|
||
``` | ||
Here we have a `posts` array that contains three posts that are associated with a `title`, the `author` of the post, and the post `id`, with three different authors. | ||
|
||
Then there is also another array that contains the authors. This array tells you who the `name`, their `twitter` and their `bio`. The author's `name` is included with the `posts` array, but if you want more information, we have to go to our second array. | ||
|
||
We are going to use these two arrays to simulate a connection to a database that won't be accessible immediately, which is a good reason to use a `Promise`. | ||
|
||
What we're going to do is we're going to create two separate functions that both going to return a `Promise` each, and then we are going to chain them together. | ||
|
||
```js | ||
function getPostById(id) { | ||
|
||
|
||
} | ||
getPostById(1) | ||
``` | ||
|
||
In setting up our function, we want it to look up in our database for the post with an `id` of 1. | ||
|
||
The first thing we need is a `Promise`, because we cannot access it from our database immediately. It has to do a round trip around back and forth to the database: | ||
|
||
```js | ||
function getPostById(id) { | ||
return new Promise((resolve, reject) => { | ||
|
||
const post = posts.find(post => post.id === id); | ||
if(post) { | ||
resolve(post) | ||
|
||
} else { | ||
reject(Error('No Post Was Found!')); | ||
} | ||
}); | ||
} | ||
|
||
getPostById(1) | ||
``` | ||
|
||
We are going to loop over every single post. Then, when the `id` matches what we want in our function, 1, we're going to find the `post` there for our control flow. The `if` is looking for a `post` matching the `id`. Once it does, it will `resolve` and give the `promise` to `post`, otherwise we are going to `reject` and say `'No Post Was Found!'`. | ||
|
||
In order to simulate this, so it takes time, what we can do is as you can just wrap this in a `SetTimeOut`, which will sort of simulate the taking 200 ms round trip. | ||
|
||
If you like to run it instantly, you don't have to do this, but it's totally up to you. | ||
|
||
```js | ||
function getPostById(id) { | ||
// create a new promise | ||
return new Promise((resolve, reject) => { | ||
// using a settimeout to mimic a database | ||
setTimeout(() => { | ||
//find the post we want | ||
const post = posts.find(post => post.id === id); | ||
if(post) { | ||
resolve(post); // send the post back | ||
|
||
} else { | ||
reject(Error('No Post Was Found!')); | ||
} | ||
}, 200); | ||
}); | ||
} | ||
|
||
getPostById(1) | ||
.then(post => { | ||
console.log(post); | ||
}) | ||
``` | ||
|
||
There we go. If we run this, we'll see there is a post immediately as an Object.. We get `postById(1)`'s result, my post. | ||
|
||
We want to do this thing that I like to call 'hydrating'. In our `posts` array, where the author of this post is just a string, `'Wes Bos'`, but I want to replace it with the the `author` object, which has my name as well as my twitter and bio. | ||
|
||
I'm going to create a new function called `hydrateAuthor`, which is going to take in the post, and then return in our getPostbyId function as a `Promise`. What's great about that is that if we return a `Promise` inside of a `.then`, we're allowed to chain another `.then` on to the next line. The whole thing looks something like this: | ||
|
||
```js | ||
function getPostById(id) { | ||
// create a new promise | ||
return new Promise((resolve, reject) => { | ||
// using a settimeout to mimic a database | ||
setTimeout(() => { | ||
//find the post we want | ||
const post = posts.find(post => post.id === id); | ||
if(post) { | ||
resolve(post); // send the post back | ||
|
||
} else { | ||
reject(Error('No Post Was Found!')); | ||
} | ||
}, 200); | ||
}); | ||
} | ||
|
||
function hydrateAuthor(post) { | ||
// create a new promise | ||
return new Promise((resolve, reject) => { | ||
// find the author | ||
const authorDetails = authors.find(person => person.name === post.author); | ||
if(authorDetails) { | ||
// "hydrate" the post object with the author object | ||
post.author = author.Details; | ||
resolve(post); | ||
} else { | ||
reject(Error('Can not find the author')); | ||
} | ||
}); | ||
} | ||
|
||
getPostById(1) | ||
.then(post => { | ||
console.log(post); | ||
return hydrateAuthor(post); | ||
}) | ||
.then(post => { | ||
}) | ||
.catch(err => { | ||
console.error(err); | ||
}) | ||
``` | ||
|
||
Let's step through all of that new code. | ||
|
||
We create our `hydrateAuthor` function that takes in the `post`. We create a new `Promise`, where we find the `author`. If there is an `author`, then we `hydrateAuthor` on our post object, which adds the `author` object to the `post`. Otherwise, it's rejected. | ||
|
||
On the end of the function, I've removed the initial `console.log`, because we don't really need it anymore, We're also going to see a `catch` for error handling. If there is an error thrown in anytime, we should be able to show the error, and allow us to debug it. | ||
|
||
If we run that in the console, you'll see that we get the is `hydrateAuthor` version of the post. Our `author` is an object that contains the `bio`, `name`, and `twitter` for whichever author wrote the post. | ||
|
||
The `catch` allows us to find errors, too. If we run `getPostById`, we'll see an error that no post was found. Similarily, if we have a typo for an author's name, we get the error, 'Cannot find the author'. | ||
|
||
It's a nice example of when you're chaining, because this is a little bit complex. There's a lot going on in here. | ||
|
||
What you can do is you can sort of sweep all this complexity into a nice little function, where you know how that works, you can test how that works. Then you can chain them together, whenever it is you that actually need them. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
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. | ||
|
||
|
||
```js | ||
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 `promise`s, 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`. | ||
|
||
```js | ||
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: | ||
|
||
```js | ||
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: | ||
|
||
```js | ||
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. | ||
|
||
```js | ||
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: | ||
|
||
```js | ||
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](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch), 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: | ||
|
||
```js | ||
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. |
Oops, something went wrong.