Reduce iterator and pipe – JavaScript – SitePoint Forums
I’m just playing with an idea.
I was working with a pipe sequence, and trying to think how I might do an early exit from the sequence, say if an empty or false value was returned mid sequence.
One idea was to write a custom reduce function that returned early on null or similar, maybe even take a comparator function to achieve this.
What I ended up playing with was reduce as a generator function that returns an iterator. This could then be used with a function like pipe and the sequence looped through with a for…of loop.
Here are the functions
// reduce generator returns an iterator
// can be used with for..of loop, ...spread etc
const _reduceIter = function* (fn, accumulator, arr) {
it = arr.entries();
if (accumulator === null) {
accumulator = it.next().value[1];
}
yield accumulator; // yield first value
for (const [i, value] of it) {
accumulator = fn(accumulator, value, i);
yield accumulator;
}
};
// pipe with iterator
const pipeIter = (fn, ...fns) => (...args) =>
_reduceIter((f, g) => g(f), fn(...args), fns);
I have been trying to think of whether this is useful or not? Thinking of use cases.
Here is a version with an async version of ‘pipeIter’
// async pipe with iterator
const pipeIterAsync = (fn, ...fns) => (...args) =>
_reduceIter(async (f, g) => g(await f), fn(...args), fns);
// quick and dirty Title Formatter
const toTitle = (str) => str
.replace(
/^\b([a-z])|\b([a-z])(?=[a-z]{3,})/g,
(_, b, c) => (b || c).toUpperCase()
)
// functions for pipe sequence
// 1. fetch JSON
const fetchJson = async (url) => {
const response = await fetch(url);
if (response.ok) {
return response.json();
}
};
// 2. Check if object has keys. Return falsy if empty or the object
const hasKeys = (obj) => Object.keys(obj).length && obj;
// 3. Check if 'completed' property is true. Return false or the object
const completed = (obj) => obj?.completed && obj;
// 4. Capitalise title property in object. Return a modified object
const capitaliseTitle = (obj) => ({ ...obj, title: toTitle(obj.title) });
async function getCompletedTask(num) {
// sequence left to right
const completedSeq = pipeIterAsync(fetchJson, hasKeys, completed, capitaliseTitle);
const url = `https://jsonplaceholder.typicode.com/todos/${num}`
let obj;
// iterate through the pipe sequence
for await (obj of completedSeq(url)) {
// if a falsy value is returned
// exit the sequence returning false
if (!obj) {
return false;
}
}
return obj;
}
A quick test
// using .then just to order these asynchronous tests
getCompletedTask(4)
.then(console.log)
.then(() => getCompletedTask(3))
.then(console.log)
.then(() => getCompletedTask(199))
.then(console.log)
Output of the quick tests
// getCompletedTask(4)
{
"userId": 1,
"id": 4,
"title": "Et Porro Tempora",
"completed": true
}
// getCompletedTask(3) not completed
false
// getCompletedTask(199)
{
"userId": 10,
"id": 199,
"title": "Numquam Repellendus a Magnam",
"completed": true
}
Here is a codepen
This was just a case of playing around with an idea. A bit of a doodle as it were. Just wondering if it has any legs?