Roll your own async compose & pipe functions

The functional programming inspired functions compose and pipe are awesome, but they only work with synchronous functions. I will show you how to write your own functions that will work with both synchronous and asynchronous functions combined - if that’s your type of thing.

Cool story bro, just show me the code already!

Fine, here you go.

Async compose function

Async pipe function

Right, now what?

If you came here just to get a code snippet, there is no shame in that, feel free to add it to your project and go on with your life! Otherwise, if you’re interested in knowing how it works, continue reading.

It might get a bit hairy but bare with me. If you are familiar with certain parts, just skip ahead.

These functions require Promises to be available in your application environment, whether that is the browser or server-side with Node. If you are developing for the modern web (not including IE), Promises are already supported in most browsers. Back in the real world, you will probably use a polyfill or some other third-party library, like bluebird, that gives you Promise support. Odds are that if you’re reading this you are probably already familiar with Promises, if not, here is a quick primer.

Simplified Primer on Promises

A Promise is an object that represents an action that will finish at some time in the future. What that means, is that you create your Promise for an asynchronous action, like an AJAX request, and when it finishes, similar to using a success callback, you call its resolve function; if it fails, you call its reject function.

The Promise object exposes 2 functions that map to the 2 “callback” functions, namely: .then() that will be called when you resolve the promise and .catch() when you reject it.

There are loads more to Promises but if you understand that, you’ll be just fine.

What’s the deal with compose & pipe?

On Medium alone there are numerous articles about the intricacies of compose and pipe which does a great job of explaining it. If you’re too lazy to google it and since you are already here, I’ll try and explain: composition is like Lego for grown-ups. It’s a way to build something more complex by using smaller simpler units. In functional programming, compose is the mechanism that composes the smaller units (our functions) into something more complex (you guessed it, another function).

So, compose takes a list of functions and returns another function that you can call with your data. An example will clear it up:

To keep things simple, we create 3 basic functions that do simple arithmetics: double, square and plus3. Nothing special here, double takes a value and multiplies it by 2; square takes a value and multiplies it with itself, and finally, plus3 does exactly that — it takes a number and adds 3 to it.

On line 5, we use our compose method to create a new composedFunction build-up of our 3 simpler functions. Each time this composedFunction is invoked it will apply these 3 functions to whatever value we pass to it. On line 7, we invoke composedFunction with the value 2.

To visualize what is happening here, picture this:

double(square(plus3(2)))

That is ugly, but it brings the point across.

The value 2 is passed into the plus3 function which produces 5. This result becomes the input for our square function, which in turn produces 25. Lastly, we invoke the double function with 25, which then produces 50.

This is an overly simple example, but the main point is to think of complex problems as the sum of a bunch of smaller problems that you can solve and compose together.

Now, imagine you have a response from an API call to retrieve a list of products, you want to map over each product and only pluck certain properties like the title, description and let’s say price. Afterwards, you also want to filter out all the products that are cheaper than $5 and lastly sort the results alphabetically.

The old you would write 3 functions: pluckProperties, getCheap and sortByTitle and do something like this:

const products = // array of product objects

const result = pluckProperties(sortByTitle(getCheap(products)))

Bonus Tip: filter first before doing anything else with a dataset so that you loop through the smallest number of entries.

The new you will use compose to create your reusable getProducts function:

const getProducts = compose(pluckProperties, sortByTitle, getCheap);

const products = // array of product objects
const result = getProducts(products)

Hopefully, you are seeing the beauty in using compose by now. If not, all hope is lost and I can’t help you. Jokes. Keep thinking about it and see where it will help you write cleaner more reusable code in your day job.

We’ll quickly touch pipe as well. It is exactly the same as compose but it applies the functions from left to right instead of right to left.

Using pipe, our example will look like this:

const getProducts = pipe(getCheap, sortByTitle, pluckProperties);

const products = // array of product objects
const result = getProducts(products)

pipe is very useful for writing step-by-step procedures, like:

pipe(logUserIn, displayNotification, redirectToHomepage)(user)

What’s cool about async compose and pipe?

Traditionally, compose and pipe only works on synchronous functions where you can pass an input value, it does something with the input and then returns an output.

Looking at the above pipe example, you will agree that the logUserIn function will most likely be an asynchronous function because you need some communication with a server/database. This won’t work then. Time to cry in the shower. 😭🚿

Let’s fix that by using our own improved pipe function:

pipe(logUserIn, displayNotification, redirectToHomepage)(user)  
  .then(() => {  
    // The user is logged in  
    // the login notification has been displayed  
    // and s/he has been redirected to the homepage  
  })

That’s it, you’re done!

Let’s break it down

For reference, here is the compose snippet again.

First, we see compose is a function that returns a function (the example below uses standard JavaScript functions instead of the more terse arrow functions):

const compose = function(...functions) {
  return function(input) {
    // ...
  }
}

It uses the ES2015 rest parameters to combine all the passed-in functions as an array of functions.

To understand the body of the inner function, it is important that you are comfortable with the reduce and reduceRight native JavaScript array methods.

reduce loops over each item in a given array and apply a function to it, with each function’s result being the input of the next item’s function. Whenever you have a list of something and you want to “reduce” it to a single value, use the reduce or reduceRight methods. These methods take 2 arguments, the first is the function that needs to run for each item in the array and the second is the optional starting value.

The easiest is to think of it as a SUM() or a TOTAL() method, for example:

const numbers = [ 1, 5, 9 ];
const total = items.reduce(function(sum, number) {
  return sum + number;
}, 0);

// total = 15 (1 + 5 + 9)

The output of each function is the input of the next. For the first number in the array (1), sum will be 0 as that is the starting value and number will be 1. The second number (5) will take 1 as the value of sum and number will be 5, which produces 6. For the third number (9), sum will, therefore, be 6 and number will be 9, which produces the final output of 15.

The only difference between the reduce and reduceRight is that reduceRight loops over the items in your array from right-to-left (last-to-first) instead of left-to-right (first-to-last).

Okay, now that we understand reduce and Promises, let’s put them together to understand the final piece of our compose function.

functions.reduceRight(
  (chain, func) => chain.then(func),
  Promise.resolve(input)
)

Instead of looping through an array of numbers as we did in our example, here we loop over an array of functions. Instead of starting with the value 0, here we start with a Promise that immediately resolves to the value we called our composed function with.

Inside our reduce/accumulator function, instead of building up a sum, we are chaining Promises together that will resolve in sequence. To visualize this using our user login example, this is what would be produced:

logUserIn(user)  
  .then(displayNotification)  
  .then(redirectToHomepage)  
  .then(result => `Do whatever we want with the ${result}`)

Wrapping up

Hope you found this useful and if you want to include these functions in your project, either copy-paste it in or grab it from NPM:

If you have better ideas or something cool to share, please let me know; would love to learn from you.

0
Subscribe to my newsletter

Read articles from Jean Pierre Erasmus directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jean Pierre Erasmus
Jean Pierre Erasmus

Full Stack JavaScript Engineer with 10+ years of experience and still in love with the craft. Over the years, I have had the privilege to lead (and be led by incredible leaders) on projects large and small. Being in the trenches with top-class people and solving real problems gets me up in the morning. I'm naturally very driven, so having to keep up with new technology suits me well.