ppipe pipes values through functions, an alternative to using the proposed pipe operator ( |> ) for ES.
Supports functions returning promises too. In that case, the result of the chain will also be a promise. This is similar to the proposed support for await in the chained functions.

const ppipe = require("ppipe");

Problems ppipe solves
Let's assume you have these functions:

const add = (x, y) => x + y;
const square = x => x * x;
const divide = (x, y) => x / y;
const double = x => x + x;

How do you pass the results from one to another?

//good old single line solution
add(divide(square(double(add(1, 1))), 8), 1);

...ugh. What about "let's declare everything" approach?

//try to get creative with variable names?
const incremented = add(1, 1);
const doubled = double(incremented);
const squared = square(doubled);
const divided = divide(squared, 8);
const result = add(divided, 1);

Meaningless variables everywhere! *The usual quote about naming things comes here*.
An ideal solution would have been having a pipe operator (|>) but we don't have it. Here is where ppipe comes in.
Order of arguments can be manipulated using the _ property of ppipe function. The result of the previous function is inserted to its place if it exists in the arguments. It can also occur more than once if you want to pass the same parameter more than once.

const _ = ppipe._;
ppipe(1)(add, 1)(double)(square)(divide, _, 8)(add, 1)();

If that is too lisp-y, you can also use ".pipe".

ppipe(1)
.pipe(add, 1)
.pipe(double)
.pipe(square)
.pipe(divide, _, 8)
.pipe(add, 1)();

And then you receive some new "requirements", which end up making the "double" function async...

async function someAPICall(x) { /* ... */ };
async function asyncDouble(x){
const result = x * 2;
await someAPICall(result);
return result;
}

Here are the changes you need to make:

await ppipe(1)
.pipe(add, 1)
.pipe(asyncDouble)
.pipe(square)
.pipe(divide, _, 8)
.pipe(add, 1);

Yes, ppipe automatically turns the end result into a promise, if one or more functions in the chain return a promise. It also waits for the resolution and passes the unwrapped value to the next function. You can also catch the errors with .catch like a standard promise or use try/catch in an async function. You meet the requirements and keep the code tidy.
For consistency, the .then and .catch methods are always available, so you don't have to care if any function in the chain is async as long as you use those.
So, later you receive some new "requirements", which make our now infamous double function return an object:

async function asyncComplexDouble(x){
const result = x * 2;
const someInfo = await someAPICall(result);
return { result, someInfo };
}

Still not a problem:

await ppipe(1)
.pipe(add, 1)
.pipe(asyncComplexDouble)
//pipe._ is also a proxy which saves the property accesses to pluck the prop from the
//previous function's result later
.pipe(square, _.result)
.pipe(divide, _, 8)
.pipe(add, 1);

...well, if you think that might not be clear, you can write it like this, too:

await ppipe(1)
.pipe(add, 1)
.pipe(asyncComplexDouble)
.pipe(x => x.result)
.pipe(square)
.pipe(divide, _, 8)
.pipe(add, 1);

...and this also works:

await ppipe(1)
.pipe(add, 1)
.pipe(asyncComplexDouble)
//promises will be unboxed and properties will be returned as getter functions
//the methods will be available in the chain as well, as shown in the next example
.result()
.pipe(square)
.pipe(divide, _, 8)
.pipe(add, 1);

Let's go one step further; what if you need to access a method from the result?

async function advancedDouble(x){
const result = x * 2;
const someInfo = await someAPICall(result);
return {
getResult() { return result },
someInfo
};
}

There you go:

await ppipe(1)
.pipe(add, 1)
.pipe(advancedDouble)
.getResult()
.pipe(square)
.pipe(divide, _, 8)
.pipe(add, 1);

Using ".with(ctx)", which calls the following function in chain with the given this value (ctx). After calling .with the chain can be continued with the methods from the ctx:

class Example {
constructor(myInt) {
this.foo = Promise.resolve(myInt);
}
addToFoo(x) {
return this.foo.then(foo => foo + x);
}
}
await ppipe(10).with(new Example(5)).addToFoo(_)

You can create an extended instance of ppipe via .extend:

const newPipe = ppipe.extend({
divide (x, y) {
return x / y;
},
log(...params) {
console.log(...params);
return params[params.length - 1];
}
});
const res = await newPipe(10)
.pipe(x => x + 1)
.divide(_, 11)
.log("here is our x: ")
.pipe(x => x + 1)

And Now for Something Completely Different

async function asyncComplexDoubleArray(x){ //I know, names are getting ridiculous
const result = x * 2;
const input = x;
const someInfo = await someAPICall(result);
return [someInfo, result, input]; //some API designed by a mad scientist
}
await asyncComplexDoubleArray(10);

await ppipe(10)
.pipe(asyncComplexDoubleArray)
.pipe((x, y) => x + y, _[1], _[2]);

await ppipe(Promise.resolve([1,2,3]))
.map(x => x + 1)
.reduce(add, 0)
.pipe(square)
.toFixed(2)
.split('.')
.pipe(_[1])

As previously shown, you can always omit the ".pipe".

await ppipe(Promise.resolve([1,2,3])).map(x => x + 1).reduce(add, 0)(square).toFixed(2).split('.')(x => x[0] + "." + x[1])

sign in to comment