@constantiner/fun-ctional
Version:
The library brings most of the familiar functional techniques (like functional composition) to asynchronous world with shining Promises
359 lines (312 loc) • 10.7 kB
JavaScript
/**
* @constantiner/fun-ctional
* The library brings most of the familiar functional techniques (like functional composition) to asynchronous world with shining Promises
*
* @author Konstantin Kovalev <constantiner@gmail.com>
* @version v0.6.6
* @link https://github.com/Constantiner/fun-ctional#readme
* @date 30 May 2019
*
* MIT License
*
* Copyright (c) 2018-2019 Konstantin Kovalev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined"
? (module.exports = factory())
: typeof define === "function" && define.amd
? define(factory)
: ((global = global || self), (global.funCtional = factory()));
})(this, function() {
"use strict";
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function() {
var self = this,
args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
/* eslint-disable unicorn/prefer-spread */
/**
* Returns a promise which resolved to array of values from a passed iterable.
* An iterable can be a promise to resolve to iterable and then to array.
*
* @param {Promise|Iterable.<*>} arrayLike Is iterable or promise to resolve to.
* @returns {Promise} A promise to resolve to resulting array.
*/
var extractArrayFromArgument =
/*#__PURE__*/
(function() {
var _ref = _asyncToGenerator(
/*#__PURE__*/
regeneratorRuntime.mark(function _callee(arrayLike) {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
_context.t0 = Array;
_context.next = 3;
return Promise.resolve(arrayLike);
case 3:
_context.t1 = _context.sent;
return _context.abrupt("return", _context.t0.from.call(_context.t0, _context.t1));
case 5:
case "end":
return _context.stop();
}
}
}, _callee);
})
);
return function extractArrayFromArgument(_x) {
return _ref.apply(this, arguments);
};
})();
var filterMergeMap = function filterMergeMap(filterFn) {
return (
/*#__PURE__*/
(function() {
var _ref = _asyncToGenerator(
/*#__PURE__*/
regeneratorRuntime.mark(function _callee(element, index, array) {
var filterResult;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
_context.next = 2;
return filterFn(element, index, array);
case 2:
filterResult = !!_context.sent;
return _context.abrupt("return", {
filterResult: filterResult,
element: element
});
case 4:
case "end":
return _context.stop();
}
}
}, _callee);
})
);
return function(_x, _x2, _x3) {
return _ref.apply(this, arguments);
};
})()
);
};
var filterResultsReducer = function filterResultsReducer(filteredArray, _ref2) {
var filterResult = _ref2.filterResult,
element = _ref2.element;
if (filterResult) {
filteredArray.push(element);
}
return filteredArray;
};
var getFilteredInParallel =
/*#__PURE__*/
(function() {
var _ref3 = _asyncToGenerator(
/*#__PURE__*/
regeneratorRuntime.mark(function _callee2(filterFn, array) {
var filterMergeMapFn, filterValues;
return regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch ((_context2.prev = _context2.next)) {
case 0:
filterMergeMapFn = filterMergeMap(filterFn);
_context2.next = 3;
return Promise.all(array.map(filterMergeMapFn));
case 3:
filterValues = _context2.sent;
return _context2.abrupt("return", filterValues.reduce(filterResultsReducer, []));
case 5:
case "end":
return _context2.stop();
}
}
}, _callee2);
})
);
return function getFilteredInParallel(_x4, _x5) {
return _ref3.apply(this, arguments);
};
})();
var getFilteredInSequence =
/*#__PURE__*/
(function() {
var _ref4 = _asyncToGenerator(
/*#__PURE__*/
regeneratorRuntime.mark(function _callee3(filterFn, array) {
var result, i, filterResult;
return regeneratorRuntime.wrap(function _callee3$(_context3) {
while (1) {
switch ((_context3.prev = _context3.next)) {
case 0:
result = [];
i = 0;
case 2:
if (!(i < array.length)) {
_context3.next = 10;
break;
}
_context3.next = 5;
return filterFn(array[i], i, array);
case 5:
filterResult = !!_context3.sent;
if (filterResult) {
result.push(array[i]);
}
case 7:
i++;
_context3.next = 2;
break;
case 10:
return _context3.abrupt("return", result);
case 11:
case "end":
return _context3.stop();
}
}
}, _callee3);
})
);
return function getFilteredInSequence(_x6, _x7) {
return _ref4.apply(this, arguments);
};
})();
var afilterGeneric = function() {
var sequence = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
return function(filterFn) {
return (
/*#__PURE__*/
(function() {
var _ref5 = _asyncToGenerator(
/*#__PURE__*/
regeneratorRuntime.mark(function _callee4(iterable) {
var sourceArray, array;
return regeneratorRuntime.wrap(function _callee4$(_context4) {
while (1) {
switch ((_context4.prev = _context4.next)) {
case 0:
_context4.next = 2;
return extractArrayFromArgument(iterable);
case 2:
sourceArray = _context4.sent;
_context4.next = 5;
return Promise.all(sourceArray);
case 5:
array = _context4.sent;
if (!sequence) {
_context4.next = 12;
break;
}
_context4.next = 9;
return getFilteredInSequence(filterFn, array);
case 9:
_context4.t0 = _context4.sent;
_context4.next = 15;
break;
case 12:
_context4.next = 14;
return getFilteredInParallel(filterFn, array);
case 14:
_context4.t0 = _context4.sent;
case 15:
return _context4.abrupt("return", _context4.t0);
case 16:
case "end":
return _context4.stop();
}
}
}, _callee4);
})
);
return function(_x8) {
return _ref5.apply(this, arguments);
};
})()
);
};
};
/**
* An asynchronous version of filter over an iterable (afilter stays for async-filter).
*
* It gets an iterable of values (or promises) as input (or promise to resolve to iterable),
* resolves them, iterates over them with filter function
* (which returns boolean where true means current value will be included in resulting array)
* and returns a promise which resolves to an array of values (filtered input iterable).
*
* It allows asynchronous filtering point-free way and can be used with asynchronous compose functions.
*
* It uses Promise.all() under the hood.
* So if filtering function is asynchronous (returns a promise) all promises are being generated at once
* and then resolved with Promise.all().
* So if any of promises will produce error (promise rejection) all the other promises will be invoked anyway.
* The advantage of this method of invoking promises it will finish earlier than sequential filter (because of Promise.all())
* but it may perform some fetches or even state modifications even in case of fail on some previous filtering steps.
*
* <pre><code>const [ first, third ] = await afilter(fetchPermissions)([somePromise1, someValue2, somePromise3]);</code></pre>
*
* It first resolves a promises passed and then pass resolutions value to the filtering function.
*
* Input iterable's values are not restricted to promises but can be any value to pass as input to functions.
*
* It also allows to handle errors like for traditional Promise:
*
* <pre><code>afilter(fetchPermissions)(somePromise1, someValue2, somePromise3).catch(e => console.error(e));</code></pre>
*
* @param {function} filterFn Is filtering function which can produce a promise (but not restricted to this).
* Function can return a promise (asynchronous filtering) or may just perform some synchronous filtering.
* So you can use it in synchronous code taking in mind it returns promise so can't be resolved immediately.
* It has three parameters (currentValue, currentIndex, array) which are already resolved (not promises).
* @returns {(iterable : Promise|Iterable.<*>) => Promise} A function which expects any values as input (resolving to Promise)
* and returns a Promise.
*/
var afilter = afilterGeneric();
return afilter;
});
//# sourceMappingURL=afilter.js.map