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;