Please note that this library is considered "done". It is still maintained and will be in the foreseeable future, but, other than adding Typescript support, no new functionality will be added. At least, there is no plan to do so. This library has an extensive test suite with 100% coverage, and it is used by at least a few well-established projects in production. The mythical "production-ready" seems to be reached :)
All bug reports and suggestions are still welcome!
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.
Installation
npm install 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);
//try to get creative with variable names?
const incremented = add(1, 1);
const doubled = double(incremented);
//...
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. If, and only if, _ doesn't exist among the arguments, the piped value will be inserted at the end.
const ppipe = require("ppipe");
const _ = ppipe._;
ppipe(1)(add, 1)(double)(square)(divide, _, 8)(add, 1)(); // 3
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)(); // 3
And then you receive some new "requirements", which end up making the "double" function async...
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); //3 (you can also use .then and .catch)
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); //3
//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); //3
//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); //3
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); //3
Some More Examples
It is possible to expand the iterable result
const addAll = (...x) => x.reduce((a, b) => a + b, 0)
ppipe([1,2,3]).map(x => x + 1).pipe(addAll, ..._)(); //9
It is possible to reach array members:
await ppipe(10)
.pipe(asyncComplexDoubleArray)
.pipe((x, y) => x + y, _[1], _[2]);
Also object properties:
ppipe(10)
.pipe(x => ({multipliers: [10,20], value: x}))
.pipe((x, y) => x * y, _.multipliers[0], _.value)(); //100
And you can omit the function altogether if you just want to extract values:
ppipe({multipliers: [10,20], value: 10}).pipe(_.value)(); //10
await ppipe({multipliers: [10,20], value: 10}).pipe(_.value); //10
And as you've seen before, you can always omit the ".pipe", as long as you know how to keep ASI in check:
ppipe({multipliers: [10,20], value: 10})(_.value)(); //10
await ppipe({multipliers: [10,20], value: 10})(_.value); //10
Advanced Functionality
Chain Methods / Properties
You can use these from the chain (after creating one with ppipe(val)
).
.with(ctx)
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(_); //15
Look at the test/test.js for more examples.
.val
Gets the current value from the chain. Will be a promise if any function in the chain returns a promise. Calling the chain with no parameters achieves the same result.
Extending Ppipe
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: ") //logs "here is our x: 1"
.pipe(x => x + 1) // 2
You can also call .extend
on the extended ppipes. It will create a new ppipe
with the new and existing extensions merged.
Testing
All the functionality is tested, with 100% coverage. This is also integrated in the build process.
To run the tests yourself, clone the repository, install the dev dependencies, and run the npm test command.
npm install
npm test
Contributing
See CONTRIBUTING.
Changelog
- v2.5.0 - placeholder can be the only argument to the .pipe, for just extracting a property or path
- v2.4.0 - allow deep property extraction via the placeholder (_.deeply.nested.prop) (test: should be able to extract array members)
- v2.3.0 - now supports expanding the placeholder (..._) (test: should support expanding the array result)
Caveats
-
This library was not written with performance in mind. So, it makes next to no sense to use it in, say, a tight loop. Use in a web-server should be fine as long as you don't have tight response-time requirements. General rule of thumb: Test it before putting it into prod. There are a lot of tests written for ppipe but none of them measure performance. I may improve the performance in the future (some low-hanging fruits) but I'd rather avoid making any guarantees. Well, there is one good news: Chrome team is working on performance improvements to the Proxy which will very positively affect ppipe performance.
-
It uses ES6 Proxies to do its magic. Proxies are not back-portable. 1.x.x versions of ppipe didn't use proxies. So you can try using an older version with a transpiler if evergreen sounds alien to you. Here is an older stable version without value extracting and context change support.
-
ppipe is not typed. No type definition exists for TypeScript nor Flow. I actually love TypeScript and would support it but the lack of variadic generic type parameters make it next to impossible to provide type definitions for ppipe. More can be read here. Also, ppipe is as dynamic as it gets, giving the ability to access virtual properties/methods which may belong to the provided context, the processed value or any of the possible extensions. TypeScripts Type System is Turing Complete, so, maybe there is a way to type all of this but I really need help about that.