Promise chains can be a powerful way to handle a series of transformations to the results of an async call. In some cases, additional promises are required along the way. In cases where there are no new promises, function composition can reduce the number of dot chained
thens you need. In this lesson, we'll look at how to take a promise chain, and reduce it down with function composition.
[00:00] I have this call to the Deck of Cards API. It creates and shuffles a deck of cards that are making a second API call with a deck id to draw cards from the deck. In this case I'm creating a deck with specific cards and drawing them all. When I make changes and run the code we'll see consistent results.
[00:16] Once the cards have been drawn the data is passed through a series of transformations in the form of a promise chain until we get the output we see here, only the clubs sorted numerically. Let's start the refactoring by taking the second fetch call, pulling it out into its own function.
[00:30] I'm just going to cut this, and up before a promise chain I'm going to define a constant, I'm going to call it draw cards. Draw cards is going to be a function that takes in an id and makes our call to fetch. I'll get this deck reference out of here. Then I want to take that id and pass that in to the URL for this API call.
[00:51] Then I can come down here into the promise chain. When I take my deck and resolve that to the deck.deck_idproperty that we get back from the API, and then I'm going to add a new then, and that's going to just call draw cards and pass that response along. We'll see that we get our result back to where it was.
[01:16] I've included the ramda library in this page. I'm going to pull in some utility methods from ramda, so I'll declare const and I'm going to pull in prop as my first one. I'll just destructure that out of ramda. I'm going to drop back down into the promise chain.
[01:30] There are a few places where all I'm doing is taking the resolution from the previous promise, grabbing a property off of the object and passing it along. This is a good example. I can replace this with a simple call to prop with my property name.
[01:46] Then I can do the same thing down here for cards. I'll say prop cards, we'll see we get our result again. Then I can do the same thing right here inside this map where I'm pulling image off of the card object. Again, I'll say prop image and everything's still working as expected.
[02:11] I can also tighten up the code on the filter and map cause, so I'm going to jump up to the top here and I'm also going to pull in filter and map from ramda. Then down here I'm simply going to replace this existing call to filter with ramda's filter.
[02:25] Which is going to take the function as its first argument, return a new function that's waiting for the object that it's going to run filter on. In this case it'll receive cards from the previous then. I can do the same thing with map.
[02:39] I'll come down here. We'll use ramda's map, which again is going to take the mapping for the projection function first followed by the data, which it'll get when this previous then resolves. I can also clean up this sort.
[02:53] To do that I'll jump back up to the top here and I'll also pull in sort by from ramda, and then down here I'm going to remove my reference to cards and I'm going to sort by. Then I'm going to remove this function, and I'm going to replace it with prop value. This is going to sort by the value property, just like I was doing before. We get our result again.
[03:16] I can also clean up this filter by pulling in prop equals from ramda. Then down here we can basically say I want to filter where the prop equals, and I have to give it the name of the property and the value, which in this case would be clubs.
[03:40] Now, I'm going to jump back up to the top of the file and I want to pull in join and compose from ramda. Then I'm going to come down here and I want to update this map. I want to use ramda's map but I also want to run my joint.
[03:59] In order to do that I'm going to grab this joint, get rid of that dot. This is going to be a composition, so I'll use compose. Then I'm going to run the map to get that array of images. Then I'm going to join them to get back the string.
[04:15] Now that I've refactored most of these then I'll like to pull some of these things out into their own functions. I'm going to start here, then I'm going to say constgetdeckid. I'm going to just sign that to the function that we get when we call propwith or property name.
[04:34] I'll replace this, just to make sure I haven't broken anything in the refactor. I'm going to do the same thing here with cards. I'll define getcards. I'll do these in order. Then I'm going to take my filter and pull that out of here.
[04:55] We'll call this just clubs, and then we'll define the function. Then sort by value. Then I'm going to pull this map to the property image out of here. I'm going to call this pluck image. We'll see that everything still works.
[05:36] Now, I can refactor this one more time and call the pluck image. I'm going to come up here and I'm actually going to use the pluck function from ramda, which is basically going to take our image and our prop and puts that together into a single function call that'll pluck the image property off of all the objects in an array of objects.
[06:00] Then I want to take this composition where I'm mapping to the image tag string and joining those into one string. I'll call that to image string. We'll use that down here in our promise chain. Then just for good measure I'll take this last function out of here. I'll call it render. That'll take in our image string and set the inner HTML on our element.
[06:38] Now that we've put all these functions out and given them meaningful names, it's pretty easy to follow what our promise chain does but there is still an awful lot of thens in there.
[06:46] I'd like to do one more thing, and what I want to do is I want to just create a composition that does all of the transformations. I'm going to define transform data, and I'm going to set that to equal or call to ramda's compose. Now I want to convert as much of this data transformation as I can into one composition.
[07:07] Fetch returns a promise, so does .JSON on that response. Getdeckid is fine, but draw cards also returns a promise. Promises aren't really composable because they're not synchronous. What I want to do is, I want to start here with getcards and then compose the rest of this together.
[07:27] In transform data because we're flowing from right to left, we're actually going to start at the end. I'm going to start with two image string which is called after pluck image, which is called after our sort. We get just clubs and get cards in there as well.
[07:50] Now I have this composition that essentially does what all of these functions do. If I take those out of there and I replace those with a single then with a call to transform data, everything will work just like it did before. I can get rid of these.
[08:10] You'll notice that I didn't include render in my composition and that's because as it stands right here this transform data function is a pure function, where render has side effects.
[08:21] It's really not pure, but if you wanted to cut this down to a single then, you could compose these and I would suggest doing it in line, just to make it very clear that we have something in pure at the end of this and everything works as expected, and we've cut this down significantly.
composeP can also be used.
It certainly can be, but the maintainers have been discussing deprecating the promise-specific versions of
pipe, This approach is less likely to go out of date with a future release of Ramda.
You can read more here if you're interested - https://github.com/ramda/ramda/pull/1869
Thanks for watching and for pointing this out for others.
in my experience it's safer to use R.path() or R.pathOr() instead of R.prop() even if there's no nesting. path() is bullet-proof against undefined values, while prop() will break