lib/promise-mix-utils.js
"use-strict";
const Operation = require('./operation');
/**
* These utilities functions aren't quite on spot in this library as it should really execut operations on Promises not on the
* downstream itself (which can be easily manipulated with a simple .then), but they might result useful sometimes/somehow to
* shorten or make more readable a Promise chain.
*/
/**
* This function checks the downstream against a check function and throws an error if the condition isn't met.
*
* @param {Function} check function to check the downstream.
* @param {*} error the error to be thrown in case the check fails.
* @returns {Promise} a Promise resolving if the check function returns true, rejecting otherwise.
*/
Promise.prototype._check = function (check, error = 'Unmet check condition.') {
return this.then(res => {
if (check(res)) {
return res;
} else {
throw error;
}
});
};
/**
* This function checks the downstream isn't undefined or throws an error.
*
* @param {*} error the error to be thrown in case the downstream is undefined/null.
* @returns {Promise} a Promise resolving if the downstream is defined, rejecting otherwise.
*/
Promise.prototype._exists = function (error = 'Downstream is undefined.') {
return this.then(res => {
if (res) {
return res;
} else {
throw error;
}
});
};
/**
* This function revives the downstream of a rejected Promise passing downstream the error itself or a new value if specified.
*
* @param {*} newDownstream the new value to pass downstream to the revived Promise.
* @returns {Promise} a Promise resolving the original caught error or the newDownstream value, if any.
*/
Promise.prototype._revive = function (newDownstream) {
return this.catch(err => {
let downstream = newDownstream ? newDownstream : err;
if (!(newDownstream instanceof Promise)) downstream = Promise.resolve(downstream);
return downstream;
});
};
/**
* This function combines the previous two checking and blocking the downstream on check failed, but immediately reviving it,
* passing downstream the error or a new value if specified.
*
* @param {Function} check function to check the downstream.
* @param {*} newDownstream the new value to pass downstream to the revived Promise.
* @returns {Promise} a Promise resolving the original caught error or the newDownstream value, if any.
*/
Promise.prototype._checkOrRevive = function (check, newDownstream) {
return this._check(check)._revive(newDownstream);
};
const checkDataNotEmptyOrVoid = (data) => {
const notNull = data !== null && data !== undefined && data !== '';
const notEmptyObject = (!(data instanceof Object) || Object.keys(data).length > 0);
const notEmptyArray = (!(data instanceof Array) || data.length > 0);
return notNull && notEmptyObject && notEmptyArray;
}
/**
* Removes all undefined, null or empty items from any downstream object or array.
* @returns {Promise} a Promise with a clean downstream.
*/
Promise.prototype._clean = function () {
return this.then((datas) => {
let cleanData;
if (datas instanceof Array) cleanData = [];
else if (datas instanceof Object) cleanData = {};
else return datas;
for (let d in datas) {
const data = datas[d];
if (checkDataNotEmptyOrVoid(data)) {
if (cleanData instanceof Array) {
cleanData.push(data);
} else {
cleanData[d] = data;
}
}
}
return cleanData;
});
};
/**
* This util hides a mux/deMux operation on a Promise downstream.
* Basically splits the downstream into an array of Promises each handling a single item of the downstream,
* uses each single downstream as input for a mapFunction which returns a new Promise to be replaced with
* the original item one.
* Finally it merges everything back with a deMux.
*
* @param {Function} mapFunction a function that accepts a single downstream item and returns a new Promise.
* @returns {Promise} the deMux of the mapped Promises.
*/
Promise.prototype._map = function (mapFunction) {
return this._mux(mux => mux.then(mapFunction).deMux());
};
/**
* Stops the downstream for the given amount of milliseconds.
*
* @param {Number} milliseconds ms milliseconds to wait.
* @returns {Promise} a Promise continuing the downstream after the given amount of milliseconds.
*/
Promise.prototype._sleep = function (milliseconds) {
return this.then(donwstream => new Promise(res => setTimeout(() => res(donwstream), milliseconds)));
};
/**
* Really simple chain method which simply logs the downstream with a given tag.
* This can be helpful to track the value of a downstream without having to write a whole .then(...) clause.
*
* @param {String} tag a tag to identify the log.
* @returns {Promise} a Promise resolving the original downstream.
*/
Promise.prototype._log = function (tag) {
return this.then(donwstream => {
// eslint-disable-next-line no-console
console.log(tag, donwstream);
return donwstream;
});
};
const loop = (prev, func, check, index = 0) => {
return prev.then(dwn => func(dwn, index)).then(dwn => {
if (check(dwn, index)) return dwn;
else return loop(Promise.resolve(dwn), func, check, ++index);
});
};
/**
* Loops the execution of a function (may be returning Promises) until a given condition is met.
* Then passes downstream the last result.
*
* @param {Function} iterationFunction the function to execute on the downstream at each iteration.
* @param {Function} breakCheck the function that evaluates if the loop should break.
* @returns {Promise} a Promise looping until the breakCheck returns true.
*/
Promise.prototype._loop = function (iterationFunction, breakCheck) {
return loop(this, iterationFunction, breakCheck);
};
/**
* Excecutes a given function on the downstream only if a certain condition is met.
*
* @param {Function} check a function to evaluate wether to execute the function or not.
* @param {*} operationToExecute an operation to be executed on the downstream if the check returns true.
* @returns {Promise} a promise resolving the downstream or the result of the function if the check returns true.
*/
Promise.prototype._when = function (check, operationToExecute) {
const operation = new Operation(operationToExecute);
return this.then(downstream => {
if (check(downstream)) return operation.get(downstream);
return Promise.resolve(downstream);
});
};
/**
* Excecutes a given function on the downstream only if a certain condition is met.
*
* @param {Function} check a function to evaluate wether to execute the function or not.
* @param {*} ifOperation an operation to be executed on the downstream if the check returns true.
* @param {*} elseOperation an operation to be executed on the downstream if the check returns false.
* @returns {Promise} a promise resolving the result of the if function or the result of the else function if the check returns true or false.
*/
Promise.prototype._ifElse = function (check, ifOperation, elseOperation) {
const ifOp = new Operation(ifOperation);
const elseOP = new Operation(elseOperation);
return this.then(downstream => {
if (check(downstream)) return ifOp.get(downstream);
return elseOP.get(downstream);
});
};
/**
* Keeps only the properties with given labels / indexes of the downstream.
*
* @param {Array} keys the array of labels / indexes of properties to keep from the downstream
* @returns {Promise} a promise resolving with the "picked" downstream.
*/
Promise.prototype._pick = function (keys) {
if (!(keys instanceof Array)) keys = [keys];
return this.then(downstream => {
let output;
if (downstream instanceof Array) {
output = [];
} else if (downstream instanceof Object) {
output = {};
} else {
throw `Cannot read properties ${keys} of ${downstream}`;
}
for (let k in keys) {
const key = keys[k];
if (output instanceof Array) {
output.push(downstream[key]);
} else {
output[key] = downstream[key];
}
}
return Promise.resolve(output);
});
};
/**
* Keeps only the properties with given labels / indexes of the downstream.
*
* @param {String} key the label to keep in the downstream.
* @returns {Promise} a promise resolving with the value of the downstream at the given key.
*/
Promise.prototype._just = function (key) {
return this.then(downstream => {
if (!(downstream instanceof Array) && !(downstream instanceof Object)) {
throw `Cannot read properties ${key} of ${downstream}`;
}
return Promise.resolve(downstream[key]);
});
};
/**
* Ignores any downstream and resolves with empty value.
*
* @returns {Promise} a promise resolving with empty downstream.
*/
Promise.prototype._ignore = function () {
return this.then(() => {
return Promise.resolve();
});
};