article cover

A Practical Example of Closures - Partially Applied Functions

Reed Bargerviews icon-
javascript
javascript

Closures are an important concept to understand in JavaScript, but how can we practically use them to improve our code?

Example of a closure

Let’s take a look at the following example of a closure.

function handleLikePost(step) {
let likeCount = 0;
return function addLike() {
likeCount += step;
return likeCount;
};
}
const doubleLike = handleLikePost(2);
console.log(doubleLike()); // 2
console.log(doubleLike()); // 4
console.log(doubleLike()); // 6

The inner function, addLike, closes over the likeCount variable. In other words, this closure allows us preserve state of our likeCount variable between function calls.

Also, since we are passing in this step argument to the outer function, it is also kept around through a closure. We haven’t realized the full benefit of this pattern yet, which enables us to keep the step value passed to the outer function around for the next function call.

What is partial application?

This approach to use higher order functions (functions passed to other functions) to preserve data through closures is called partial application.

Partial application refers to the fact that we’re applying some, but not all of the arguments of a function and waiting for the rest of the arguments. But what we haven’t done in this example is pass any arguments to the double like function. What would our code look like if we did this?

Let’s say we are in the process of building a social media application.

We’re already keeping track of likes with this handleLike function, but before that, let’s say we need to get our user’s posts and comments from an external API.

For this function, we can get the appropriate data we need by providing a given url and the type of data that we need from a given endpoint. Let’s say we need to get our posts and comments in multiple pages in our app. As a result, we have to pass in our baseUrl and endpoint every time we use it.

function getData(baseURL, endpoint) {
fetch(`${baseURL}${endpoint}`)
.then((res) => res.json())
.then((data) => data);
}
getData("https://jsonplaceholder.typicode.com", "/posts");
getData("https://jsonplaceholder.typicode.com", "/comments");

Since our app depends on these two data types, even though we’re using a function that helps us cut down on our repetition, we need to provide our baseUrl every time we call it.

The benefit of partially applied functions

Here’s the benefit of higher order functions to us as developers—they let us have functions with certain values that are preserved, so to speak. With this, it enables us to make our functions more clear as to what they do. They let us write better code by allowing our functions to have single responsibilities.

Let’s rewrite this example with partial application. The first thing we can do is write a function within getData. We don’t have to give this function a name like we did before. We just want to return it:

function getData(baseURL, endpoint) {
return function () {
fetch(`${baseURL}${endpoint}`)
.then((res) => res.json())
.then((data) => data);
};
}

And now instead of having the getData function accept both arguments, baseURL and endpoint, have the inner function accept endpoint.

function getData(baseURL) {
return function (endpoint) {
fetch(`${baseURL}${endpoint}`)
.then((res) => res.json())
.then((data) => console.log(data));
};
}

Just a quick question—when we call getData once what will we get?

We’ll get our inner anonymous function returned to us. And when we do that, we can assign this generic getData function to something more specific. We’re using the following baseURL to get post and comment data: jsonplaceholder.typicode.com, we could put it in a variable called getSocialMediaData:

const getSocialMediaData = getData("https://jsonplaceholder.typicode.com");

At this point, we can already see an immediate benefit. By partial application, when we lock in this baseURL value through a closure, we get a more clearly defined, single responsibility for this function, which results in a more clear name. Whenever we use it, we know exactly what we’ll be doing—getting social media data.

How do we use it?

All we have to do is call it, now with the argument required for the route. Let’s try the route needed to get comments data, /comments:

const getSocialMediaData = getData("https://jsonplaceholder.typicode.com");
getSocialMediaData("/comments");
// (500) [{…}, {…}, {…}, {…}, {…}]

And when we call it, we see all of our comments. If we wanted to get our posts, what would we do?

We don’t need to provide our baseUrl anymore. That’s being saved through getData’s closure. But we do need to pass the differing route for the post data, /posts:

getSocialMediaData("/posts");
// (100) [{…}, {…}, {…}, {…}, {…}]

We can reuse this partially applied getSocialMediaData function for as many routes as we need.

What is the takeaway for partial application?

With a partially applied function, you pass some arguments and you get back a function that locks those argument values in place through a closure and can call with some other data.

In other words, a partially applied function reduces the total number of arguments for a function, while giving us a pattern for functions to remember data passed to it.

Extending the example

But we can extend this even further. Right now, as you can see, in this then callback, we’re just taking the data that we get and logging it to the console. Naturally within our app, we want a way to display that to our users. Is there a way that we can extend our partially applied function for it to accept a callback so we can manipulate the data as we see fit?

We could just add another anonymous inner function, before fetching the data and pass through a callback function, that will be called in the inner body and therefore will wrap our final data:

function getData(baseUrl) {
return function (route) {
return function (callback) {
fetch(`${baseUrl}${route}`)
.then((res) => res.json())
.then((data) => callback(data));
};
};

And also make sure to return this most inner function. So what can we do now in executing our partially applied function?

Now instead of getting data, when we call getSocialMediaData('/posts'), we get a returned function that takes a callback. So let’s assign this inner function to a new variable, getSocialMediaPosts, when using the posts route and getSocialMediaComments for the comments route:

const getSocialMediaComments = getSocialMediaData("/comments");
const getSocialMediaPosts = getSocialMediaData("/posts");

Then what can we do? We can pass a callback function to both of these new functions, and since we are getting their data in the form of arrays in both cases, we could iterate over both arrays using the .forEach() method and maybe we just want their title in either case, so we’ll just console.log each comment’s title.

In the real-world we would display them in our app:

const getSocialMediaPosts = getSocialMediaData("/posts");
getSocialMediaPosts((posts) => {
posts.forEach((post) => console.log(post.title));
});

And finally, let’s see what our partially applied function would look like as an arrow function. See if you can convert these functions to a series of arrow functions if you can. We just need to remove the function keyword and the return keywords, plus the parentheses around parameters, and the curly braces and we can put everything on one line and it’ll work as before:

const getData = (baseUrl) => (route) => (callback) =>
fetch(`${baseUrl}${route}`)
.then((res) => res.json())
.then((data) => callback(data));

Some JS developers like writing their higher order functions this way, but I find the previous style better to understand. I would try to understand both and use whichever is more readable for you.

Summary

This might all be a little hard to wrap your head around, so I would recommend playing around with this example or our previous handleLike example so you can better understand what’s going on here, the order in which we call these functions, and what we can do with such higher order function patterns.

The takeaway is that now instead of having one function do multiple things for us, partial application lets our functions have just single, clearly defined responsibilities.

Know that partial application isn’t a technique you reach for very often, but it’s a powerful tool to improve the role of our functions, their reusability and separation of concerns.



course cover
Join The 2020 JS Bootcamp Video Course 🏕️
Code ArtistryWatch Now
Reed Barger
Professional JS developer who loves to write. Here to make you a better developer, faster.