I've got lots of ideas for this article, but I'm not sure where to start. If you're interested in making code more readable and using tree shaking, you should definitely check out the article.
If you've ever developed with JavaScript, you've probably come across this type of code.
const someInstance = new Class();
someInstance.do('jump').some().thing('dance').and().read();
I like this style because it could be very easy to read and you see what is connected. Plus, you don't have to move data from one function to another because it's all saved in the instance.
On top of the fact that this only works if the functions aren't async, there's another issue. It's not going to be easy to tree shake the content if you're not using it. If you use this in the client, you have to move the whole library with all the connected functions, which is a huge overhead.
So, how can we keep things readable, use async functions and let Vite and co. tree shake all unused code?
The answer's right there in the headline: "pipe."
From a functional standpoint, implementing pipe is pretty straightforward. However, the types did pose a challenge. I've put everything together into a library and published it on npm. pipe-and-combine.
const inc = (by: number) => (x: number) => x + by;
const dec = (by: number) => (x: number) => x - by;
const multiplyBy = (by: number) => (x: number) => x * by;
const divideBy = (by: number) => (x: number) => x / by;
const toStr = () => (x: number) => x.toString();
// prepare the pipeline
const pipeline = pipe(inc(2), multiplyBy(7), dec(7), divideBy(3), toStr())
// Executing the pipeline
pipeline(7)
One advantage is that it's not tied to any object, so you can use any function as long as the input and output match the function before and after.
The example I've given is pretty simple, but at least you can pipe everything. You'll have the same options as with chaining and classes. Generic functions might be a little more tricky, but there's a solution. I'll cover this in more detail in another article.
Top comments (4)
The tree-shaking part makes totally sense. The example functions above, though, appear to be all specialised versions of the RxJS
map()
operator.Checking the pipe-and-combine github website, it doesn't seem to have an equivalent of
filter()
,scan()
, etc, is that correct? Are you planning to make filtering and reducing available, as well?I am thinking about more helper functions, but the ground idea is to give a easy way to write your own functions to pipe them.
I wanna avoid such a big lib as RxJS where you have nearly endless possibilities but need to learn a hole dictionary to use it well.
Yeah, there are some 200 operators in Rx, but 99% of the time you use less than 10.
90% of the time you use maybe... 5?
For the remaining 1% I guess you would turn to your fav LLM anyway.
So, will you not risk reinventing the same thing anyway? Maybe the same 10 core operators, but with different names and syntaxes?
Recreating a new streams library may be perfectly fine, don't get me wrong. I'm working on two myself... so, in which cases should somebody choose pipe-and-combine over something like RxJS?
The helper functions should just cover some common use cases, so not everyone has to reinvent the wheel, but the biggest benefit should be to to just but your own functions into the pipe. Specially generic functions that are easy to reuse. That should be the biggest benefit.
I wrote an other article about the generics, I linked with a series.
Here is also a more detailed article on how I solved the generic problem: dropanote.de/en/blog/20250721-type...