Here's one for you:
There are a couple of answers that talk about curry, and partial-application.
And that's a great direction.
But once you really get higher-order functions, you can make this stuff really clean and easy to work with.
const curry = (f, ...initialArgs) => (...extraArgs) => {
const args = [...initialArgs, ...extraArgs];
return args.length >= f.length ? f(...args) : curry(f, ...args);
};
So what does that do?
It lets you pass in a function, and gives you a function. Until you have passed in enough arguments to run the function, it's going to keep passing you another function that expects more arguments.
What good is that?
const multiply = curry((x, y) => x * y);
const double = multiply(2);
const triple = multiply(3);
double(2); // 4
triple(9); // 27
Now it's really easy to define something like your test.
const notEqual = curry((test, x) => test !== x);
// you could do it like this, to reuse `notFoo`
const notFoo = notEqual("foo");
samples.filter(notFoo);
// you could do it like this, if you don't need `notFoo`
samples.filter(notEqual("foo"));
But wait! There's more!
const filter = curry((predicate, array) => array.filter(predicate));
const removeFoos = filter(notEqual("foo"));
removeFoos(samples);
removeFoos(items);
removeFoos(otherStuff);
Now I have a function that filters out foos and I can just pass it arrays whenever I feel like it.
Last one for now:
const compose = (...fs) => x => fs.reduceRight((x, f) => f(x), x);
Instead of writing
h(g(f(x)));
Compose lets me write
const hgf = compose(h, g, f);
hgf(x);
hgf(y);
hgf(z);
// it's read from right to left
const tto = compose(three, two, one);
// or from bottom to top
const tsf = compose(
third,
second,
first
);
// because it runs like
y = third(second(first(x)));
So now, let's try something wild...
// lib functions (Ramda would work fine)
const map = curry((transform, array) => array.map(transform));
const reduce = curry((summarize, seed, array) =>
array.reduce(summarize, seed));
const flatMap = curry((transform, array) =>
array.map(transform).reduce((a, b) => a.concat(b), []));
// business functions
const castToEmployee = personData => new Employee(personData);
const isWorking = ({ active }) => active;
const removeSuperiors = curry((user, employee) =>
employee.role <= user.role);
const customEmployeeCriteria = (criteria, employee) => { /*...*/ };
const removeDuplicates = (arr, employee) =>
arr.some(person => person.id === employee.id)
? arr
: arr.concat(employee);
Library Code
const performCustomSearch = searchCriteria =>
filter(cutomEmployeeCriteria(searchCriteria));
const getAuthorizedEmployeeList = currentUser =>
filter(removeSuperiors(currentUser));
const buildEmployees = compose(
filter(isWorking),
map(castToEmployee),
);
const cleanResults = compose(
filter(removeBrokenItem),
map(removePrivateMembers),
reduce(removeDuplicates, []),
);
const handleEmployeeRequest = (currentUser, searchCriteria) => compose(
cleanResults,
performCustomSearch(searchCriteria),
getAuthorizedEmployeeList(currentUser),
buildEmployees
);
API Code
//(maybe /employees/?search={...}&token=123)
router.get("/employees", (req, res) => {
PersonService.getAll()
.then(handleEmployeeRequest(req.user, req.query.search))
.then(filteredEmployees => res.json(filteredEmployees));
});
And we're done.
Easy as pie.