promiseUtil.js

/**
 * This module contains utility methods for promises.
 *
 * @exports store/promiseUtil
 * @namespace
 */
const promiseUtil = {};

/**
 * Asynchronous forEach relying on promises that works in serial rather than parallell.
 * It invokes a function on each item only after the promise from the previous item
 * in the array has succeeded.
 * If one of the promises fails the forEach promise fails altogheter.
 * The result is provided in an array if the items is an array.
 * In the case where the items is an object the same object is returned
 * but with the values replaced with the result of the promise.
 * (The function is applied to the value of each key in the items object.)
 *
 * @param {array|object} items
 * @param {function} func a function that is applied to each item and must return a promise
 * @returns {Promise}
 */
promiseUtil.forEach = (items, func) =>
  new Promise((resolve, reject) => {
    let arr;
    let cursor;
    const onFailure = (err) => {
      reject(err);
    };
    if (Array.isArray(items)) {
      const results = [];
      arr = items.slice();
      cursor = (result) => {
        results.push(result);
        if (arr.length > 0) {
          return promiseUtil
            .toPromise(func(arr.shift()))
            .then(cursor, onFailure);
        }
        resolve(results);
        return undefined;
      };
      if (arr.length === 0) {
        resolve(results);
      } else {
        promiseUtil.toPromise(func(arr.shift())).then(cursor, onFailure);
      }
    } else if (typeof items === 'object') {
      arr = Object.keys(items);
      let itemKey;
      const onSuccess = (result) => {
        items[itemKey] = result;
        cursor();
      };
      cursor = () => {
        if (arr.length > 0) {
          itemKey = arr.shift();
          promiseUtil
            .toPromise(func(items[itemKey]))
            .then(onSuccess, onFailure);
        } else {
          resolve(items);
        }
      };
      cursor();
    }
  });

/**
 * Makes sure a value is a promise, if needed wraps it as a promise.
 * If the value the false boolean it is interpreted as a reject.
 *
 * @param {any|Promise} value the value to wrap in a promise, if it already is a promise it is returned.
 * @return {Promise}
 */
promiseUtil.toPromise = (value) => {
  if (
    typeof value === 'object' &&
    value !== null &&
    typeof value.then === 'function'
  ) {
    return value;
  }
  if (value === false) {
    return Promise.reject(value);
  }
  return Promise.resolve(value);
};

/**
 * Use setTimeout with promise. This is useful when you want to do things like
 * Promise.all(
 *  promise1,
 *  promiseUtil.delay(1000),
 * ]);
 * @param millisecs
 * @returns {Promise<any>}
 */
promiseUtil.delay = (millisecs) =>
  new Promise((resolve) => setTimeout(resolve, millisecs));

export default promiseUtil;