Skip to Content
Course content

111: Partial Application

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I see this mistake all the time when developers start building more complex event-driven systems. They have a general-purpose function and they want to use it in a specific context, but they end up triggering the function immediately instead of scheduling it for later.

function logEvent(category, message) {
  console.log(`[${category}] ${message}`);
}

const saveButton = document.querySelector('#save');
const deleteButton = document.querySelector('#delete');

// I want the save button to log as 'SYSTEM' 
// and the delete button to log as 'CRITICAL'
saveButton.addEventListener('click', logEvent('SYSTEM', 'User saved changes'));
deleteButton.addEventListener('click', logEvent('CRITICAL', 'User deleted record'));

The "Immediate Execution" Trap

If you ran the code above, you'd notice two things: first, the console logs 'SYSTEM' and 'CRITICAL' the moment the page loads. Second, when you actually click the buttons, nothing happens. Why?

The problem is that by writing logEvent('SYSTEM', 'User saved changes'), you aren't giving the event listener a function to call later. You are calling the function right now and handing the event listener the result of that call. Since logEvent doesn't return anything, you've effectively written addEventListener('click', undefined).

Creating Specialized Functions with .bind()

This is where partial application comes in. Partial application is the act of taking a function with multiple arguments and "fixing" some of them to create a new, more specialized function.

In JavaScript, the most direct way to do this is using .bind(). The first argument of .bind() sets the this context (we'll use null here since we don't need a specific context), and every argument after that becomes "pre-filled" in the original function.

function logEvent(category, message) {
  console.log(`[${category}] ${message}`);
}

// We "partially apply" the category argument.
// These are now functions waiting to be called.
const logSystem = logEvent.bind(null, 'SYSTEM');
const logCritical = logEvent.bind(null, 'CRITICAL');

saveButton.addEventListener('click', () => logSystem('User saved changes'));
deleteButton.addEventListener('click', () => logCritical('User deleted record'));

Now, logSystem is a brand new function that only needs one argument (the message) because 'SYSTEM' is already baked into it. I personally find this much cleaner than writing the full logEvent('SYSTEM', ...) call every single time throughout a large file.

The Modern Alternative: Closures

While .bind() is the classic approach, most of the engineers I work with these days prefer using arrow functions to achieve partial application. It's often more readable and avoids the awkward null argument for the context.

const logSystem = (message) => logEvent('SYSTEM', message);
const logCritical = (message) => logEvent('CRITICAL', message);

Under the hood, this is doing the same thing: it's capturing the 'SYSTEM' string in a closure and returning a function that uses it. Whether you use .bind() or an arrow function, the goal is the same: reducing the arity (the number of arguments) of a function to make it more reusable and less error-prone.




📋 Practical Task

Building a Specialized API Request Tool

You are building a dashboard that fetches data from two different API categories: users and posts. Instead of passing the base URL and the category every time you make a request, you need to use partial application to create specialized helper functions.

Your Task:

  1. Start with this general function:
    function apiRequest(baseUrl, category, endpoint) {
        return `Fetching from ${baseUrl}/${category}/${endpoint}...`;
      }
  2. Use .bind() or an arrow function to create a specialized function called fetchUser. This function should have 'https://api.example.com' and 'users' already applied.
  3. Create another specialized function called fetchPost. This function should have 'https://api.example.com' and 'posts' already applied.
  4. Test your new functions by calling fetchUser('profile') and fetchPost('123'). They should return the correct full URL strings.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.