Reduce iterator and pipe

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?