omnibelt/map-serial-p.js

const curry = require('ramda/src/curry');

const objAccumulator = (accumulator, transformed, original) => { accumulator[original[0]] = transformed; };
const aryAccumulator = (accumulator, transformed) => { accumulator.push(transformed); };

const makeEvaluator = (func, iterator, accFunc, accumulator) => {
  const evaluator = async () => {
    const next = iterator.next();
    if (next.done) { return; }
    accFunc(accumulator, await func(next.value), next.value);
    await evaluator();
  };
  return evaluator;
};

/**
 * A promise map helper, runs an async function on each item one after the other.
 * Given an asynchronous function and an iterable or an object, this will evaluate the
 * function against each value of the iterable.
 *
 * If the given iterable is neither iterable nor an object, it is just returned.
 *
 * Note: An iterable will always return a promise for an array. An object will return a promise
 * for an object.
 *
 * @func
 * @memberof module:omnibelt
 * @name mapSerialP
 *
 * @param {Function} func - An async function
 * @param {Iterable} iterable - Object or iterable
 * @return {Promise} A promise that will resolve to either an array or object when iteration is done
 * @summary Function -> Iterable -> Promise<iterable>
 */
const mapSerialP = curry(async (func, iterable) => {
  if (!iterable) { return iterable; }

  let accumulator, evaluator;
  if (iterable[Symbol.iterator]) {
    accumulator = [];
    evaluator = makeEvaluator(
      func, iterable[Symbol.iterator](), aryAccumulator, accumulator);
  } else if (typeof(iterable) === 'object') {
    accumulator = {};
    evaluator = makeEvaluator(
      func, Object.entries(iterable)[Symbol.iterator](), objAccumulator, accumulator);
  } else {
    return iterable;
  }

  await evaluator();

  return accumulator;
});

module.exports = mapSerialP;