A Short Introduction to Pipeline Operator, and Piping, in JavaScript
Alex Devero
Posted on November 15, 2021
Functional programming provides many useful concepts. One of these concepts is pipeline operator and piping. This tutorial will help you understand what pipeline operator and piping are, how they work and how to use them. You will also learn how to create your own pipe function in JavaScript.
A brief introduction
The pipeline operator is one of those features that has been discussed for a long time but never became a feature of JavaScript language. This changed and pipeline operator entered as a draft the stage 1 of TC39 process. In 2021, it moved from stage 1 to stage 2.
This means that pipeline operator is still not a stable feature of JavaScript language and its specification can change. However, there is already a Babel plugin that allows us to work with this feature. That said, we don't even need the plugin or the feature to emulate what pipeline operator does.
We can take existing JavaScript syntax and create our own function that will lead to similar results as the pipeline operator. But before we do that, let's take a look at the pipeline operator and piping.
Piping made simple
The idea of piping functions may sound difficult to understand, but it is not. Put simply, piping about taking some input and passing it into a function and then sending it into another function. This way, you can take some value as an input and send it through a sequence of functions to get one value as an output.
One way to get this done is by using method chaining. With this approach, you take a value and call some method on it. Then, instead of calling another method on the result of previous call separately, you "chain" the next method the first.
// Chaining example with string:
const sentence = ' There - is some - mess around. '
// Modifying the string with method chaining:
const cleanedSentence = sentence
.replace(/-/g, ' ')
.replace(/\s+/g, ' ')
.trim()
console.log(cleanedSentence)
// Output:
// 'There is some mess around.'
Another option is using piping, but without the pipeline operator. This solution works well with custom functions. Instead of chaining functions, you pass one function call as argument to another function call. This way, you can pass a value returned by one function to another to get the result you need.
// Piping example:
// Define some functions:
const add = (num) => num1 + 10
const subtract = (num) => num1 - 5
const multiply = (num) => num1 * 9
// Use piping to pass value through cascade of functions:
const num = multiply(add(subtract(15)))
console.log(num)
// Output:
// 180
There is one problem with this. Your code can quickly become pile of unreadable mess as you add more and more function calls. Now, let's take a look at how we can handle this with the help of pipeline operator.
The pipeline operator
In JavaScript, the pipeline operator uses a very specific syntax. It uses this "pipe" |>
symbol. When you want to use this operator you have to put it on a specific place. This place is between the value you want to pass to a function call and the function you want to call.
If you want to pipe multiple functions, you put the |>
symbol between each of them. Remember that you don't put the |>
symbol after the last function. The last function is the last thing in the chain. Let's demonstrate pipeline operator by rewriting the example with piping to this new syntax.
// Without pipeline operator:
const add = (num1, num2) => num1 + 10
const subtract = (num1, num2) => num1 - 5
const multiply = (num1, num2) => num1 * 9
const num = multiply(add(subtract(15)))
// Log the value of "num":
console.log(num)
// Output:
// 180
// With pipeline operator:
const numPiped = 15 |> add |> subtract |> multiply
// Log the value of "num":
console.log(numPiped)
// Output:
// 180
// Notes:
// 1. Value 15 gets passed to add() fn
// 2. The value returned by add() fn is passed to subtract()
// 3. The value returned by subtract() fn is passed to multiply()
// 4. The value returned by multiply() fn is assigned to numPiped variable
As you can see, our code is much more readable when we use the pipeline operator. It may take a moment to get used to the new syntax and some differences, such as missing parentheses in function calls.
Custom piping function
The pipeline operator looks useful. The problem that may prevent us from starting using it is that it is only in stage 2. This means it is not guaranteed it will make it to the JavaScript specification. Even if it will eventually make it, we don't know when. It already took a lot of time for the operator to make it to stage 2.
Fortunately, there are two options. The first one is the Babel plugin. This plugin will allow as to use pipeline operator right now, before it reaches stage 3 or 4. Another option is creating our own custom piping function using current JavaScript. Let's focus on the second option, and create the custom function.
This piping function will be simple. What we need is a function that accepts unknown number of arguments. This function will iterate over all arguments, which will be functions, and call each. Each function call will return a value. Our piping function will take each value and add it to the previous.
For each call, our piping function will use the previously returned value as an argument for the current call. After the last function is called, our piping function will add the last value to the accumulator of previous values and return the final value. This may sound complicated, but we can get this done easily with reduce() method.
// Functions to pipe:
const add = (num1, num2) => num1 + 10
const subtract = (num1, num2) => num1 - 5
const multiply = (num1, num2) => num1 * 9
// Custom piping function
/**
* Pipes functions and returns a single value
* @param {Array} args - array composed of initial value and functions
* @return {any}
*/
const pipeFn = (...args) => args.reduce((acc, fn) => fn(acc));
// Testing custom piping function:
const numPiped = pipeFn(15, add, subtract, multiply)
console.log(numPiped)
// Output:
// 180
As you can see, the custom piping function is very simple. It is composed of two things, array of arguments and one reduce method. One thing some developers may not like is the initial value passed as the first argument. One way to fix this is by using currying.
We can remove the initial value from the arguments array with functions in the first call and move it to a separate function call.
// Functions for piping:
const add = (num1, num2) => num1 + 10
const subtract = (num1, num2) => num1 - 5
const multiply = (num1, num2) => num1 * 9
// Updated piping function:
const pipeFn = (...args) => val => args.reduce((acc, fn) => fn(acc), val);
// Test:
const numPiped = pipeFn(add, subtract, multiply)(15)
console.log(numPiped)
// Output:
// 180
Final note
It is worth repeating that at the moment of writing this article, pipeline operator is at stage 2. Although there is a Babel plugin that can transpile pipeline operator syntax into JavaScript modern browsers can understand I would use this feature in production code. A lot can change and a lot can break.
For anyone who wants to use this feature I would suggest using some custom implementation, either one we used or some alternative. This will ensure your code works no matter the changes in the operator proposal. And when the operator is out, you can easily migrate your custom implementation if you want.
Conclusion: A short introduction to pipeline operator, and piping, in JavaScript
Pipeline operator makes it easy to use the concept of piping functions while keeping your code readable and short. This operator is not an official part of JavaScript yet. However, this doesn't mean we can use it today, either directly with the help of Babel or indirectly through custom implementation.
Posted on November 15, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 22, 2024