Sitemap
Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to democratize free coding education for the world.

Implementing Pipe in JS

5 min readDec 30, 2024

--

Implementing Pipe in JavaScript: Sync and Async Made Easy

Press enter or click to view image in full size

Hello, beautiful people on the internet.

The pipe operator (often seen in functional programming) enables you to process data through a sequence of functions, each transforming the output of the previous one.

While JavaScript doesn’t have a native pipe operator yet, we can implement it ourselves for both synchronous and asynchronous use cases.

In this blog, we’ll explore the implementation of a pipe function for synchronous and asynchronous workflows. Let’s dive in!

The Pipe Operator: What and Why?

The pipe operator is a functional programming concept that allows you to chain multiple functions together. It’s like a pipeline where data flows through each function sequentially. Instead of writing deeply nested function calls, you can write cleaner and more readable code.

Example Use Case

Imagine you have two functions:

  • reverseSentence: Reverses the words in a sentence.
  • capitalizeWords: Capitalizes the first letter of each word.

Here’s the desired transformation:

console.log(capitalizeWords(reverseSentence('My name is Deep')));
// Output: "Deep Is Name My"

While this works, it can get messy with more transformations. This is where the pipe operator shines.

Synchronous Pipe Implementation

Let’s start by creating a pipeSync function to handle synchronous transformations. It takes a sequence of functions as arguments and returns a function that applies these transformations in order.

Implementation (normal)

const pipeSync = (...funcs) => {
// The returned function takes an argument `args`, which will be passed through the pipeline of functions.
return args => {
// Initialize `result` with the initial value of `args`.
let result = args;

// Loop through each function in the `funcs` array and apply it to the current `result`.
for (let func of funcs) {
result = func(result); // Update `result` with the output of the current function.
}

// Return the final result after all functions have been applied.
return result;
};
};

Implementation (using reduce)

const pipeSync = (...funcs) => {
// The returned function takes `args` as the initial value to start the pipeline.
return args => {
// Use `reduce` to apply each function in `funcs` to the accumulated result (`acc`), starting with the initial `args`.
return funcs.reduce((acc, func) => {
// Apply the current function `func` to the accumulated result `acc` and return it for the next iteration.
return func(acc);
}, args); // `args` is the initial value passed into the pipeline.
};
};

How It Works

  1. pipeSync accepts any number of functions as arguments.
  2. It returns a new function that applies these functions to the input in sequence.
  3. Each function’s output becomes the input for the next function.

Example Usage

const reverseSentence = sentence => sentence.split(' ').reverse().join(' ');
const capitalizeWords = sentence => sentence.split(' ').map(word => `${word[0].toUpperCase()}${word.slice(1)}`).join(' ');

const pipeSyncExample = pipeSync(
reverseSentence,
capitalizeWords
);
console.log(pipeSyncExample('My name is Deep'));
// Output: "Deep Is Name My"

Asynchronous Pipe Implementation

Working with promises and asynchronous functions? Let’s extend the pipe concept to handle them. Enter pipeAsync!

Implementation (normal)

const pipeAsync = (...funcs) => {
// The returned async function takes `args`, which can be a promise or a value.
return async args => {
// Wait for the initial `args` to resolve (if it's a promise).
let result = await args;

// Loop through each function in the `funcs` array, awaiting each function's result.
for (let func of funcs) {
// Apply the current function `func` to the result and wait for its resolved value.
result = await func(result);
}

// Return the final result after all asynchronous functions have been applied.
return result;
};
};

Implementation (using reduce)

const pipeAsync = (...funcs) => {
// The returned function takes `args`, which is the initial value that will be passed through the pipeline.
return args => {
// Use `reduce` to chain the asynchronous functions together.
return funcs.reduce((acc, func) => {
// For each function, the accumulator `acc` is a Promise, and we resolve it using `then`.
// Each function `func` is applied to the resolved value of `acc`.
return acc.then(result => func(result));
}, Promise.resolve(args)); // Start the chain by wrapping the initial `args` in a resolved Promise.
};
};

How It Works

  1. pipeAsync is similar to pipeSync, but it handles asynchronous functions by awaiting their results.
  2. The input can also be a promise, making it flexible for real-world scenarios.

Example Usage

Let’s say we have two asynchronous functions:

const reverseSentenceAsync = sentence => {
return new Promise(resolve => {
console.log("Reversing:", sentence);
setTimeout(() => {
resolve(sentence.split(' ').reverse().join(' '));
}, 3000);
});
};

const capitalizeWordsAsync = sentence => {
return new Promise(resolve => {
console.log("Capitalizing:", sentence);
setTimeout(() => {
resolve(sentence.split(' ').map(word => `${word[0].toUpperCase()}${word.slice(1)}`).join(' '));
}, 3000);
});
};
const pipeAsyncExample = pipeAsync(
reverseSentenceAsync,
capitalizeWordsAsync
);
pipeAsyncExample('My name is Deep').then(result => {
console.log("Final Result:", result);
});

Output

Reversing: My name is Deep
Capitalizing: Deep is name My
Final Result: Deep Is Name My

Real-World Use Cases of Pipe Operators

1. Data Transformation

In data pipelines, you often need to perform multiple transformations on raw data. The pipe operator makes these transformations clean and composable.

2. Middleware Chaining

In web frameworks like Express, middleware functions can be thought of as a pipeline that processes requests sequentially.

3. Functional Composition

The pipe operator embodies the essence of functional programming: composing small, reusable functions to solve complex problems.

Conclusion

The pipe operator is a powerful tool for writing clean, readable, and maintainable code. Whether working with synchronous or asynchronous workflows, implementing your pipe function in JavaScript can simplify complex function chains and improve code clarity.

Get Deep Shah’s stories in your inbox

Join Medium for free to get updates from this writer.

Try using pipeSync and pipeAsync in your projects and see how they transform your coding experience!

I’m always looking to improve and would love to hear your thoughts! If you have any suggestions, feedback, or topics you’d like me to cover, please feel free to share them in the comments below. Your insights are greatly appreciated! 🙂

Thank you for being a part of the community

Before you go:

--

Stackademic
Stackademic

Published in Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to democratize free coding education for the world.