UNPKG

async-af

Version:

The asynciest of async libs there ever was or ever will be...AsyncAF!?

1,444 lines (1,339 loc) 62.9 kB
/*! * async-af/esm v7.0.39 * * AsyncAF (The asynciest of async libs there ever was or ever will be...AsyncAF!?) * (https://async-af.js.org/AsyncAF) * * Copyright (c) 2017-present, Scott Rudiger (https://github.com/ScottRudiger) * * This source code is licensed under the MIT license found in this library's * GitHub repository (https://github.com/AsyncAF/AsyncAF/blob/master/LICENSE). */ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } const nameFunction = function nameFunction(fn, name) { return Object.defineProperty(fn, 'name', { value: name, configurable: true }); }; const createNewlessClass = Class => { const { name } = Class; const Newless = function Newless() { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return new Class(...args); }; Newless.prototype = Class.prototype; Object.setPrototypeOf(Newless, Class); Newless.prototype.constructor = Newless; return nameFunction(Newless, name); }; /** * adds prototype/static methods to AsyncAF or AsyncAfWrapper * * see {@link AsyncAfWrapper AsyncAfWrapper} for an example of how to cherry-pick AsyncAF methods you'd like to use rather than pulling in the entire AsyncAF library; * * for something different, the following shows how to add custom methods to AsyncAF & AsyncAfWrapper * * **Example** * * say you want to extend AsyncAF with your own prototype method that acts on an array of numbers or promises that resolve to numbers and naively adds them up * * let's call it sumAF; here's some code: * * ```js * // sumAF.js * * const sumAF = function () { * return this.then(nums => Promise.all(nums)) * .then(nums => nums.reduce((sum, num) => sum + num)); * }; * * export default sumAF; * ``` * * pull in {@link AsyncAF AsyncAF} or {@link AsyncAfWrapper AsyncAfWrapper} and `sumAF` to the file you'd like to use it in: * * ```js * // otherFile.js * * import AsyncAF from 'async-af'; // or import AsyncAF from '@async-af/wrapper'; * import sumAF from './sumAF'; * ``` * * then, call `use` on `AsyncAF` and pass in `sumAF` wrapped in an object to the first parameter, `prototypeMethods`: * * ```js * // otherFile.js * // ... * * AsyncAF.use({sumAF}); * ``` * * ready! now your custom prototype method will be available on AsyncAF * * ```js * // otherFile.js * // ... * * const promises = [1, 2, 3].map(n => Promise.resolve(n)); * * const sum = AsyncAF(promises).sumAF() * * AsyncAF.logAF(sum); * // @otherFile.js:10:9: * // 6 * // in 0.001 secs * ``` * * if you'd like to add a static method to AsyncAF, `use` accepts a second optional argument `staticMethods`; for example: * * ```js * const staticNoop = () => {}; * * AsyncAF.use({}, {staticNoop}); * * AsyncAF.staticNoop(); // noop * ``` * * @static * @param {Object} prototypeMethods an Object containing the prototype methods you'd like to use * @param {Object=} staticMethods an Object containing the static methods you'd like to use * @returns {undefined} adds prototype/static methods to AsyncAF or AsyncAfWrapper * @since 3.0.0 * @see AsyncAF * @see AsyncAfWrapper * @see {@tutorial TOO_MANY_IMPORTS} * @memberof AsyncAfWrapper * @alias AsyncAfWrapper#use */ const use = function use(prototypeMethods) { let staticMethods = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (typeof prototypeMethods !== 'object') throw TypeError('prototypeMethods param accepts an Object containing the prototypeMethods you\'d like to add to the AsyncAF prototype, or an empty Object'); if (typeof staticMethods !== 'object') throw TypeError('staticMethods param accepts an Object containing the staticMethods you\'d like to add to AsyncAF'); Object.assign(this.prototype, prototypeMethods); Object.assign(this, staticMethods); }; const inSeries = new WeakMap(); const series = { inSeries: { get() { return inSeries.get(this); } }, /** * indicates that the next method invoked should be performed in series * * when you need to perform a method in series rather than in parallel, prepend the method with `series`; e.g.: * ```js * AsyncAF(promises).series.forEachAF(callback) * ``` * * `series` can currently be chained with: * - {@link AsyncAF#everyAF everyAF} * - {@link AsyncAF#filterAF filterAF} * - {@link AsyncAF#findAF findAF} * - {@link AsyncAF#findIndexAF findIndexAF} * - {@link AsyncAF#forEachAF forEachAF} * - {@link AsyncAF#includesAF includesAF} * - {@link AsyncAF#indexOfAF indexOfAF} * - {@link AsyncAF#lastIndexOfAF lastIndexOfAF} * - {@link AsyncAF#mapAF mapAF} * - {@link AsyncAF#reduceAF reduceAF} * - {@link AsyncAF#someAF someAF} * * @example * import delay from 'delay'; // {@link https://www.npmjs.com/package/delay} * * const nums = [2, 1]; * * // perform a serial forEach by chaining {@link AsyncAF#series series} and {@link AsyncAF#forEachAF forEachAF} * (async () => { * const start = Date.now(); * * await AsyncAF(nums).series.forEachAF(async num => { * await delay(num * 1000); * console.log(num, `at ~${Date.now() - start} ms`); * }); * * console.log(`total: ~${Date.now() - start} ms`); * })(); * * // logs: * // 2 'at ~2000 ms' * // 1 'at ~3000 ms' * // total: ~3000 ms * * * // perform a parallel forEach by omitting {@link AsyncAF#series series} * (async () => { * const start = Date.now(); * * await AsyncAF(nums).forEachAF(async num => { * await delay(num * 1000); * console.log(num, `at ~${Date.now() - start} ms`); * }); * * console.log(`total: ~${Date.now() - start} ms`); * })(); * * // logs: * // 1 'at ~1000 ms' * // 2 'at ~2000 ms' * // total: ~2000 ms * * @function series * @returns {AsyncAF.<any>} returns an instance of AsyncAF that will perform the next method invocation serially * @since 7.0.0 * @see {@link AsyncAF#io io} (alias) * @memberof AsyncAF# */ series: { get() { inSeries.set(this, !this.inSeries); return this; } }, /** * `io` (in order) indicates that the next method invoked should be performed in series * * when you need to perform a method in series rather than in parallel, prepend the method with `io`; e.g.: * ```js * AsyncAF(promises).io.forEachAF(callback) * ``` * * `io` is an alias for `series`; see {@link AsyncAF#series series's documentation} for more * @function io * @returns {AsyncAF.<any>} returns an instance of AsyncAF that will perform the next method invocation serially * @since 7.0.0 * @see {@link AsyncAF#series series} (alias) * @memberof AsyncAF# */ io: { get() { return this.series; } } }; const dataStore = new WeakMap(); class AsyncAfWrapperProto { constructor(data) { dataStore.set(this, Promise.resolve(data)); } then(resolve, reject) { return this.constructor(dataStore.get(this).then(resolve, reject)); } catch(reject) { return this.then(null, reject); } finally(onFinally) { return dataStore.get(this).finally(onFinally); } } AsyncAfWrapperProto.use = use; Object.defineProperties(AsyncAfWrapperProto.prototype, _objectSpread({}, series, { [Symbol.toStringTag]: { value: 'AsyncAF' } })); /** * empty AsyncAF class wrapper * * AsyncAfWrapper is one option for cherry-picking only the methods you'd like to use in your code; {@link AsyncAfWrapper#use use}, {@link AsyncAF#series series}, and {@link AsyncAF#io io} are the only methods initially available on AsyncAfWrapper; see example below * * **Note:** while AsyncAfWrapper is a class, it can create instances with or without the `new` keyword * * **Example** * * say you only want to use {@link AsyncAF#mapAF mapAF}, {@link AsyncAF#filterAF filterAF}, {@link AsyncAF#forEachAF forEachAF}, and {@link AsyncAF#logAF logAF} instead of pulling in the entire AsyncAF library * * first, install the separate packages (e.g., for npm): * * `$ npm install --save @async-af/{wrapper,map,filter,foreach,log}` * * or, if on Windows: * * `$ npm install --save @async-af/wrapper @async-af/map @async-af/filter @async-af/foreach @async-af/log` * * then import the packages * ```js * import AsyncAF from '@async-af/wrapper'; // aliasing 'AsyncAfWrapper' as 'AsyncAF' * import mapAF from '@async-af/map'; * import filterAF from '@async-af/filter'; * import forEachAF from '@async-af/foreach'; * import logAF from '@async-af/log'; * ``` * * _if you'd like to save some vertical screen real estate and cut the imports down to one line, see_ {@tutorial TOO_MANY_IMPORTS} * * then call {@link AsyncAfWrapper#use use}, including all prototype methods you'd like to add to AsyncAfWrapper's prototype in the first argument, `prototypeMethods` and all static methods you'd like to add to AsyncAfWrapper in the second optional argument, `staticMethods` * ```js * AsyncAF.use({ // prototype methods go in the first argument * mapAF, * filterAF, * forEachAF * }, { // static methods go in the second argument * logAF * }); * ``` * * ready to go! * ```js * const promises = [1, 2, 3].map(n => Promise.resolve(n)); * * AsyncAF(promises).mapAF(n => n * 2).filterAF(n => n !== 4).forEachAF(n => console.log(n)); * // logs 2 then 6 * * AsyncAF.logAF(promises); * // @filename.js:24:9: * // [ 1, 2, 3 ] * // in 0.003 secs * ``` * * **protip:** you can use the same technique to add your own custom prototype or static methods to AsyncAfWrapper or even to the main AsyncAF class; see {@link AsyncAfWrapper#use use} for an example * @param {any} data the data to be wrapped by the AsyncAF class; can be promises or non-promises * @returns {Object} returns an instance of AsyncAfWrapper wrapping the passed in data * @since 3.0.0 * @see AsyncAF * @see {@link AsyncAfWrapper#use use} * @see {@tutorial TOO_MANY_IMPORTS} * @class AsyncAfWrapper */ const AsyncAfWrapper = createNewlessClass(class AsyncAfWrapper extends AsyncAfWrapperProto {}); /* eslint-disable no-console */ const wrappedLog = function wrappedLog() { console && console.log && console.log(...arguments); }; const wrappedWarn = function wrappedWarn() { console && console.warn && console.warn(...arguments); }; /** * Sets logging options for {@link AsyncAF#logAF logAF} * * accepts an options object with the following optional properties: * - label (`Boolean`) - set to `false` to disable logging the location of calls to logAF * - duration (`Boolean`) - set to `false` to disable logging the time it takes (in secs) to complete each call to logAF * - labelFormat (`String`|`Function`) - alters the format of logAF labels; choose between `file` (*default*), `path`, `parent`, `arrow`, or a custom string or function * * ```js * const promise = new Promise(resolve => setTimeout( * () => resolve(1), 1000) * ); * ``` * **default logging** * ```js * logAF(promise, 2); * * // @filename.js:24:1: * // 1 2 * // in 0.998 secs * ``` * **turn off label** * ```js * logAF.options({ label: false }); * logAF(promise, 2); * * // 1 2 * // in 0.999 secs * ``` * **turn off duration** * ```js * logAF.options({ duration: false }); * logAF(promise, 2); * * // @filename.js:24:1: * // 1 2 * ``` * **change labelFormat** * * &#9679; file (*default*) * * ```js * logAF.options({ labelFormat: 'file' }); * logAF(promise, 2); * * // @filename.js:24:1: * // 1 2 * // in 0.998 secs * ``` * &#9679; path * * ```js * logAF.options({ labelFormat: 'path' }); * logAF(promise, 2); * * // @/Path/to/current/directory/filename.js:24:1: * // 1 2 * // in 0.997 secs * ``` * &#9679; parent * * ```js * logAF.options({ labelFormat: 'parent' }); * logAF(promise, 2); * * // @parentDirectory/filename.js:24:1: * // 1 2 * // in 0.998 secs * ``` * &#9679; arrow * * ```js * logAF.options({ labelFormat: 'arrow' }); * logAF(promise, 2); * * // ========================> 1 2 * // in 0.999 secs * ``` * * &#9679; custom (create your own labelFormat) * - to set a custom labelFormat, set it to any string other than the formats above * * ```js * logAF.options({ * labelFormat: 'I logged this:' * }); * logAF(promise, 2); * * // I logged this: 1 2 * // in 1.000 secs * ``` * * - labelFormat also accepts a function with access to an object containing the location variables `file`, `path`, `parent`, `arrow`, `line`, and `col` * * e.g., to set the labelFormat to `file:line:col =>`: * ```js * logAF.options({ * labelFormat: ({file, line, col}) => `${file}:${line}:${col} =>` * }); * logAF(promise, 2); * * // filename.js:24:1 => 1 2 * // in 0.998 secs * ``` * * and just to demonstrate all the location variables in one custom format: * ```js * logAF.options({ * labelFormat: ({arrow, line, col, parent, file, path}) => * `${arrow} * line: ${line} * col: ${col} * parent: ${parent} * file: ${file} * path: ${path} * ` * }); * logAF(promise, 2); * * // ========================> * // line: 24 * // col: 1 * // parent: parentDirectory/ * // file: filename.js * // path: /Full/path/to/the/parentDirectory/ * // 1 2 * // in 0.998 secs * ``` * * to reset `logAF.options` to its default values, call `logAF.options.reset` * ```js * logAF.options.reset(); * * // options are now: * // label: true, * // duration: true, * // labelFormat: 'file' * ``` * * @static * @param {Object} options the options for logAF * @param {Boolean} [options.label=true] set to false to turn off the label * @param {Boolean} [options.duration=true] set to false to turn off duration * @param {String|Function} [options.labelFormat=file] see examples for sample label formats * @returns {undefined} sets the options for logAF * @see {@link AsyncAF#logAF logAF} * @see logAF.options.reset to reset options to default * @memberof AsyncAF * @alias AsyncAF#logAF_options */ const logAfOptions = function logAfOptions() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; const { label, duration, labelFormat } = options; if (typeof label === 'boolean') logAF$1.label = label; if (typeof duration === 'boolean') logAF$1.duration = duration; if (labelFormat) if (typeof labelFormat === 'string' || typeof labelFormat === 'function') logAF$1.labelFormat = labelFormat;else logAF$1.wrappedWarn('Warning: logAF labelFormat option must be set to \'file\' (default), \'path\', \'parent\', \'arrow\', or a custom string or function\n'); }; function _templateObject4() { const data = _taggedTemplateLiteral(["/"]); _templateObject4 = function _templateObject4() { return data; }; return data; } function _templateObject3() { const data = _taggedTemplateLiteral(["/"]); _templateObject3 = function _templateObject3() { return data; }; return data; } function _templateObject2() { const data = _taggedTemplateLiteral(["/"]); _templateObject2 = function _templateObject2() { return data; }; return data; } function _templateObject() { const data = _taggedTemplateLiteral([":"]); _templateObject = function _templateObject() { return data; }; return data; } const custom = (format, fullPath, arrow) => { if (typeof format === 'string') return format; let [path, line, col] = fullPath.split(_templateObject()); // eslint-disable-line prefer-const path = path.split(_templateObject2()); const file = path.pop(); path = path.join(_templateObject3()); const parent = "".concat(path.split(_templateObject4()).pop(), "/"); path += '/'; return format({ path, line, col, file, parent, arrow }); }; function _templateObject4$1() { const data = _taggedTemplateLiteral(["/"]); _templateObject4$1 = function _templateObject4() { return data; }; return data; } function _templateObject3$1() { const data = _taggedTemplateLiteral(["/"]); _templateObject3$1 = function _templateObject3() { return data; }; return data; } function _templateObject2$1() { const data = _taggedTemplateLiteral(["/"]); _templateObject2$1 = function _templateObject2() { return data; }; return data; } function _templateObject$1() { const data = _taggedTemplateLiteral(["\n"], ["\\n"]); _templateObject$1 = function _templateObject() { return data; }; return data; } const setFormat = labelFormat => { const error = new Error(); /* istanbul ignore if */ if (!error.stack) return ''; const [targetLine] = error.stack.split(_templateObject$1()).filter((_, i, lines) => /logAF(?:\s+|\s+\[.+\]\s+)\(/.test(lines[i ? i - 1 : i]) || /logAfStub(?:\s+|\s+\[.+\]\s+)\(/.test(lines[i])); const fullPath = targetLine.slice(targetLine.indexOf(_templateObject2$1())).replace(')', ''); const target = fullPath.lastIndexOf(_templateObject3$1()); const formats = { file() { return "@".concat(fullPath.slice(target + 1), ":\n"); }, path() { return "@".concat(fullPath, ":\n"); }, parent() { const start = fullPath.slice(0, target).lastIndexOf(_templateObject4$1()) + 1; return "@".concat(fullPath.slice(start), ":\n"); }, arrow() { return '========================>'; } }; return formats[labelFormat] ? formats[labelFormat]() : custom(labelFormat, fullPath, formats.arrow()); }; /** * logs items to the console in the order given * * if any items are a promise, they will first be resolved in parallel and then logged * * ```js * import { logAF } from 'async-af'; * * const promise = new Promise(resolve => setTimeout( * () => resolve(2), 1000) * ); * * logAF(1, promise, 3); * * // @filename.js:6:12: * // 1 2 3 * // in 0.998 secs * ``` * * **Note:** since logAF returns a promise, the items in the previous example would be logged *after* any synchronous calls to `console.log` * * to produce in-order logging with any surrounding calls to `console.log`, `await` logAF: * ```js * logAF.options({ label: false, duration: false }); * * (async () => { * console.log(1); * // ...some code * await logAF(promise); * // ...some more code * console.log(3); * })(); * * // 1 * // 2 * // 3 * ``` * * **experimental feature**: the label may not work correctly in all environments; to turn the label off, set `label` to `false` in {@link AsyncAF#logAF_options logAF.options}, where you can also change the label's format * * @static * @param {any} items The items to print (log to the console) * @returns {Promise<undefined>} returns a `Promise` that logs items to the console * @see log (alias) * @see {@link AsyncAF#logAF_options logAF.options} to turn the label off or change its format * @see logAF.options.reset to reset options to default * @since 3.0.0 * @memberof AsyncAF * @alias AsyncAF#logAF */ const logAF = function logAF() { for (var _len = arguments.length, items = new Array(_len), _key = 0; _key < _len; _key++) { items[_key] = arguments[_key]; } if (logAF.label) items.unshift(logAF.setFormat(logAF.labelFormat)); const start = Date.now(); return Promise.all(items).then(toLog => { if (logAF.duration) { const end = Date.now(); const numberOf = ((end - start) / 1000).toFixed(3); toLog.push("\n in ".concat(numberOf, " secs")); } logAF.wrappedLog('', ...toLog); }); }; Object.defineProperties(logAF, { wrappedLog: { value: wrappedLog, writable: true }, wrappedWarn: { value: wrappedWarn, writable: true }, setFormat: { value: setFormat, writable: true }, options: { value: logAfOptions, writable: true } }); (logAF.options.reset = function logAfOptionsReset() { logAF.label = true; logAF.labelFormat = 'file'; logAF.duration = true; })(); var logAF$1 = nameFunction(logAF, 'logAF'); /* eslint-disable no-unused-vars, valid-jsdoc */ const permissiveIsArrayLike = function permissiveIsArrayLike(obj) { return Array.isArray(obj) || obj != null && obj.length != null; }; const promiseAllWithHoles = promises => new Promise((resolve, reject) => { const length = promises.length >>> 0; const result = Array(length); let pending = length; let i = length; if (!length) return resolve(result); const settlePromise = i => Promise.resolve(promises[i]).then(value => { if (i in promises) result[i] = value; if (! --pending) resolve(result); }, reject); while (i--) settlePromise(i); }); const serial = arr => function resolveSerially(resolved, i) { const { length } = resolved; if (!length) return Promise.resolve(resolved); const hole = !(i in arr); return Promise.resolve(arr[i]).then(el => { if (!hole) resolved[i] = el; if (i === length - 1) return resolved; return resolveSerially(resolved, i + 1); }); }(Array(arr.length >>> 0), 0); const parallel = function parallel(arr, mapper) { let thisArg = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined; return promiseAllWithHoles(arr, el => el).then(!mapper ? undefined : arr => promiseAllWithHoles(Array.prototype.map.call(arr, mapper, thisArg))); }; /** * creates a new `Array` with the results of calling a provided function on every element in the original array * * if any elements are a `Promise`, they will first be resolved in parallel and then processed * * *Note*: if you'd rather resolve and process elements in series, consider using `series.mapAF` or its alias, `io.mapAF` * * @param {callback} callback function that produces an element of the new `Array` * * `callback` accepts three arguments: * - `currentValue` value of the current element being processed in the array * - `index`*`(optional)`* index of `currentValue` in the array * - `array`*`(optional)`* the array that mapAF is being applied to * @param {Object=} thisArg value to use as `this` when executing `callback` * @returns {Promise.<Array>} `Promise` that resolves to a new `Array` with each element being the result of calling `callback` on each original element * @example * * const promises = [1, 2].map(n => Promise.resolve(n)); * * * // basic usage * const doubled = AsyncAF(promises).mapAF(el => el * 2); * * console.log(doubled); // Promise that resolves to [2, 4] * * AsyncAF.logAF(doubled); // logs [2, 4] * * * // using .then * AsyncAF(promises).mapAF(el => el * 3).then(tripled => { * console.log(tripled); // logs [3, 6] * }); * * * // inside an async function * (async () => { * const quadrupled = await AsyncAF(promises).mapAF( * el => el * 4 * ); * console.log(quadrupled); // logs [4, 8] * })(); * @since 3.0.0 * @see map (alias) * @see {@link AsyncAF#series series.mapAF} * @memberof AsyncAF# */ const mapAF = function mapAF(callback$$1) { let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; return this.then(arr => { if (!permissiveIsArrayLike(arr)) throw TypeError("mapAF cannot be called on ".concat(arr, ", only on an Array or array-like Object")); if (typeof callback$$1 !== 'function') throw TypeError("".concat(callback$$1, " is not a function")); return this.inSeries ? serial(arr).then(arr => arr.reduce((map, el, i, arr) => map.then(map => { map[i] = Promise.resolve(callback$$1.call(thisArg, el, i, arr)); return promiseAllWithHoles(map); }), Promise.resolve(Array(arr.length >>> 0)))) : parallel(arr, callback$$1, thisArg); }); }; /** * executes a callback function on each element in an array * * if any elements are a `Promise`, they will first be resolved in parallel and then processed * * *Note*: if you'd rather resolve and process elements in series, consider using `series.forEachAF` or its alias, `io.forEachAF` * * @param {callback} callback function to execute for each element * * `callback` accepts three arguments: * - `currentValue` value of the current element being processed in the array * - `index`*`(optional)`* index of `currentValue` in the array * - `array`*`(optional)`* the array that forEachAF is being applied to * @param {Object=} thisArg value to use as `this` when executing `callback` * @returns {Promise.<undefined>} `Promise` that resolves to `undefined` * @example * * const promises = [1, 2].map(n => Promise.resolve(n)); * * * AsyncAF(promises).forEachAF(el => { * console.log(el); // logs 1 then 2 * }); * @since 3.0.0 * @see forEach (alias) * @see {@link AsyncAF#series series.forEachAF} * @memberof AsyncAF# */ const forEachAF = function forEachAF(callback$$1) { let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; return this.then(arr => { if (!permissiveIsArrayLike(arr)) throw TypeError("forEachAF cannot be called on ".concat(arr, ", only on an Array or array-like Object")); if (typeof callback$$1 !== 'function') throw TypeError("".concat(callback$$1, " is not a function")); return (this.inSeries ? serial(arr).then(arr => arr.reduce((expr, el, i, arr) => expr.then(() => Promise.resolve(callback$$1.call(thisArg, el, i, arr))), Promise.resolve())) : parallel(arr, callback$$1, thisArg)).then(() => {}); }); }; /** * creates a new `Array` with all elements that pass the test implemented by the provided callback function * * if any elements are a `Promise`, they will first be resolved in parallel and then tested * * *Note*: if you'd rather resolve and test elements in series, consider using `series.filterAF` or its alias, `io.filterAF` * * @param {callback} callback function that tests each element of the array; return `true` to keep the element, `false` to filter it out * * `callback` accepts three arguments: * - `currentValue` value of the current element being processed in the array * - `index`*`(optional)`* index of `currentValue` in the array * - `array`*`(optional)`* the array that `filterAF` is being applied to * @param {Object=} thisArg value to use as `this` when executing `callback` * @returns {Promise.<Array>} `Promise` that resolves to a new `Array` with the elements that pass the test; if no elements pass the test, the promise will resolve to an empty array * @example * * const promises = [1, 2, 3].map(n => Promise.resolve(n)); * * * // basic usage * const odds = AsyncAF(promises).filterAF(n => n % 2); * * console.log(odds); // Promise that resolves to [1, 3] * * AsyncAF.logAF(odds); // logs [1, 3] * * * // using .then * AsyncAF(promises).filterAF(n => n % 2).then(odds => { * console.log(odds); // logs [1, 3] * }); * * * // inside an async function * (async () => { * const odds = await AsyncAF(promises).filterAF( * n => n % 2 * ); * console.log(odds); // logs [1, 3] * })(); * @since 3.0.0 * @see filter (alias) * @see {@link AsyncAF#series series.filterAF} * @memberof AsyncAF# */ const filterAF = function filterAF(callback$$1) { let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; return this.then(arr => { if (!permissiveIsArrayLike(arr)) throw TypeError("filterAF cannot be called on ".concat(arr, ", only on an Array or array-like Object")); if (typeof callback$$1 !== 'function') throw TypeError("".concat(callback$$1, " is not a function")); return (this.inSeries ? serial : parallel)(arr).then(arr => (this.inSeries ? arr.reduce((bools, el, i, arr) => bools.then(bools => { bools[i] = callback$$1.call(thisArg, el, i, arr); return Promise.all(bools); }), Promise.all([])) : parallel(arr, callback$$1, thisArg)).then(bools => arr.filter((_, i) => bools[i]))); }); }; /* eslint-disable no-unused-vars, valid-jsdoc */ /* eslint-disable prefer-rest-params */ /** * applies a function against an accumulator and each element in an array (from left to right) to reduce it to a single value * * if any elements are a `Promise`, they will first be resolved in parallel and then processed in series * * *Note*: if this behavior is not desirable, consider using `series.reduceAF` or its alias, `io.reduceAF`; that way, if any elements are a `Promise`, they will both be resolved in series _and_ processed in series * * @param {callback} callback function to execute for each element * * `callback` accepts up to four arguments: * - `accumulator` accumulates the callback's return values; the accumulated value previously returned in the last invocation of the callback, or initialValue, if supplied * - `currentValue` value of the current element being processed in the array * - `index`*`(optional)`* index of `currentValue` in the array * - `array`*`(optional)`* the array that `reduceAF` is being applied to * @param {any=} initialValue value to use as the first argument to the first call of the callback; if no initial value is supplied, the first element in the array will be used; note: calling reduceAF on an empty array with no initial value will throw an error * @returns {Promise.<any>} `Promise` that resolves to the reduced value * @example * * const promises = [1, 2, 3].map(n => Promise.resolve(n)); * * * // basic usage * const sum = AsyncAF(promises).reduceAF((sum, num) => sum + num); * * console.log(sum); // Promise that resolves to 6 * * AsyncAF.logAF(sum); // logs 6 * * * // using .then * AsyncAF(promises).reduceAF((sum, num) => sum + num).then(sum => { * console.log(sum); // logs 6 * }); * * * // inside an async function * (async () => { * const sum = await AsyncAF(promises).reduceAF((sum, num) => sum + num); * console.log(sum); // logs 6 * })(); * * * // using an initial value * AsyncAF(promises).reduceAF((sum, num) => sum + num, 12) // Promise that resolves to 18 * @since 3.1.0 * @see reduce (alias) * @see {@link AsyncAF#series series.reduceAF} * @memberof AsyncAF# */ const reduceAF = function reduceAF(callback /* , initialValue */ ) { return this.then(arr => { if (!permissiveIsArrayLike(arr)) throw TypeError("reduceAF cannot be called on ".concat(arr, ", only on an Array or array-like Object")); if (typeof callback !== 'function') throw TypeError("".concat(callback, " is not a function")); const length = arr.length >>> 0; if (!length && arguments.length === 1) throw TypeError('reduceAF cannot be called on an empty array without an initial value'); if (!length) return arguments[1]; const hole = i => !(i in arr); let i = 0; let acc; if (arguments.length === 2) { [, acc] = arguments; } else { while (hole(i)) i++; acc = arr[i++]; } return (this.inSeries ? serial : parallel)(arr).then(arr => { const reduceAF = (acc, i) => Promise.resolve(acc).then(acc => Promise.resolve(!hole(i) ? callback(acc, arr[i], i, arr) : acc).then(acc => i === length - 1 ? acc : reduceAF(acc, i + 1))); return reduceAF(acc, i); }); }); }; /** * tests whether all elements in the array pass the test implemented by the provided callback function * * if any elements are a `Promise`, they will first be resolved in parallel and then tested * * *Note*: since `everyAF` is run in parallel, `callback` will be run on all elements even if one of the first few elements fails the test; if this behavior is not desireable, consider using `series.everyAF` or its alias, `io.everyAF` * * @param {callback} callback function that tests each element of the array * * `callback` accepts three arguments: * - `currentValue` value of the current element being processed in the array * - `index`*`(optional)`* index of `currentValue` in the array * - `array`*`(optional)`* the array that `everyAF` is being applied to * @param {Object=} thisArg value to use as `this` when executing `callback` * @returns {Promise.<Boolean>} `Promise` that resolves to `true` if the callback function returns a truthy value for every array element; otherwise, `false` * @example * * const promises = [1, 2, 3].map(n => Promise.resolve(n)); * * * // basic usage * const allAreOdd = AsyncAF(promises).everyAF(n => n % 2); * * console.log(allAreOdd); // Promise that resolves to false * * AsyncAF.logAF(allAreOdd); // logs false * * * // using .then * AsyncAF(promises).everyAF(n => n % 2).then(allAreOdd => { * console.log(allAreOdd); // logs false * }); * * * // inside an async function * (async () => { * const allAreNums = await AsyncAF(promises).everyAF( * n => typeof n === 'number' * ); * console.log(allAreNums); // logs true * })(); * @since 3.2.0 * @see every (alias) * @see {@link AsyncAF#series series.everyAF} * @memberof AsyncAF# */ const everyAF = function everyAF(callback$$1) { let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; return this.then(arr => { if (!permissiveIsArrayLike(arr)) throw TypeError("everyAF cannot be called on ".concat(arr, ", only on an Array or array-like Object")); if (typeof callback$$1 !== 'function') throw TypeError("".concat(callback$$1, " is not a function")); const length = arr.length >>> 0; return this.inSeries ? !length && true || function seriesEveryAF(arr, i) { const hole = !(i in arr); return Promise.resolve(arr[i]).then(el => { arr[i] = el; return Promise.resolve(!hole && callback$$1.call(thisArg, el, i, arr)).then(bool => { if (!bool && !hole) return false; if (i === length - 1) return true; return seriesEveryAF(arr, i + 1); }); }); }(Array.prototype.slice.call(arr), 0) : parallel(arr, callback$$1, thisArg).then(bools => bools.every(Boolean)); }); }; /** * tests whether at least one element in the array passes the test implemented by the provided callback function * * if any elements are a `Promise`, they will first be resolved in parallel and then tested * * *Note*: since `someAF` is run in parallel, `callback` will be run on all elements even if one of the first few elements passes the test; if this behavior is not desireable, consider using `series.someAF` or its alias, `io.someAF` * * @param {callback} callback function that tests each element of the array * * `callback` accepts three arguments: * - `currentValue` value of the current element being processed in the array * - `index`*`(optional)`* index of `currentValue` in the array * - `array`*`(optional)`* the array that `someAF` is being applied to * @param {Object=} thisArg value to use as `this` when executing `callback` * @returns {Promise.<Boolean>} `Promise` that resolves to `true` if the callback function returns a truthy value for any array element; otherwise, `false` * @example * * const promises = [1, 2, 3].map(n => Promise.resolve(n)); * * * // basic usage * const someAreEven = AsyncAF(promises).someAF(n => n % 2 === 0); * * console.log(someAreEven); // Promise that resolves to true * * AsyncAF.logAF(someAreEven); // logs true * * * // using .then * AsyncAF(promises).someAF(n => n % 2 === 0).then(someAreEven => { * console.log(someAreEven); // logs true * }); * * * // inside an async function * (async () => { * const someAreStrings = await AsyncAF(promises).someAF( * n => typeof n === 'string' * ); * console.log(someAreStrings); // logs false * })(); * @since 3.3.0 * @see some (alias) * @see {@link AsyncAF#series series.someAF} * @memberof AsyncAF# */ const someAF = function someAF(callback$$1) { let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; return this.then(arr => { if (!permissiveIsArrayLike(arr)) throw TypeError("someAF cannot be called on ".concat(arr, ", only on an Array or array-like Object")); if (typeof callback$$1 !== 'function') throw TypeError("".concat(callback$$1, " is not a function")); const length = arr.length >>> 0; return this.inSeries ? (length || false) && function seriesSomeAF(arr, i) { const hole = !(i in arr); return Promise.resolve(arr[i]).then(el => { arr[i] = el; return Promise.resolve(!hole && callback$$1.call(thisArg, el, i, arr)).then(bool => { if (bool && !hole) return true; if (i === length - 1) return false; return seriesSomeAF(arr, i + 1); }); }); }(Array.prototype.slice.call(arr), 0) : parallel(arr, callback$$1, thisArg).then(bools => bools.some(Boolean)); }); }; const sameValueZero = (a, b) => a === b || Number.isNaN(a) && Number.isNaN(b); /** * determines whether an array, string, or array-like object includes a certain element or string, returning true or false as appropriate * * *Note*: when called on an array or array-like object, `includesAF` is run in parallel and all elements will be resolved even if one of the first few elements is a match; if this behavior is not desireable, consider using `series.includesAF` or its alias, `io.includesAF` * * @param {any} searchItem the element or string to search for * @param {Number=} fromIndex the index at which to begin searching for `searchItem`; a negative value searches from the index of `array/string.length - fromIndex`; defaults to `0` * @returns {Promise.<Boolean>} `Promise` that resolves to `true` if `searchItem` is found; otherwise, `false` * @example * * // includesAF on an array of promises * const nums = [1, 2, 3].map(n => Promise.resolve(n)); * * AsyncAF(nums).includesAF(2); // Promise that resolves to true * * AsyncAF(nums).includesAF(5); // Promise that resolves to false * * AsyncAF(nums).includesAF(1, 1); // Promise that resolves to false * * AsyncAF(nums).includesAF(3, -1); // Promise that resolves to true * * // includesAF on a promise-wrapped string * const string = Promise.resolve('test string'); * * AsyncAF(string).includesAF('test'); // Promise that resolves to true * * AsyncAF(string).includesAF('nope'); // Promise that resolves to false * * AsyncAF(string).includesAF('test', 5); // Promise that resolves to false * * AsyncAF(string).includesAF('string', -6); // Promise that resolves to true * * // includesAF on an array-like object * (async function () { * if (await AsyncAF(arguments).includesAF(2)) { * console.log('2 is included'); * } * })(1, 2, 3); // logs '2 is included' * * @since 3.4.0 * @see includes (alias) * @see {@link AsyncAF#series series.includesAF} * @memberof AsyncAF# */ const includesAF = function includesAF(searchItem) { let fromIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; return this.then(arrOrStr => { if (!permissiveIsArrayLike(arrOrStr)) throw TypeError("includesAF cannot be called on ".concat(arrOrStr, ", only on an Array, String, or array-like Object")); const length = arrOrStr.length >>> 0; const fromIdx = fromIndex | 0; return typeof arrOrStr === 'string' ? arrOrStr.includes(searchItem, fromIdx) : this.inSeries ? (length || false) && function seriesIncludesAF(i) { return Promise.resolve(arrOrStr[i]).then(el => { if (sameValueZero(el, searchItem)) return true; if (i >= length - 1) return false; return seriesIncludesAF(i + 1); }); }(Math.max(fromIdx >= 0 ? fromIdx : length - Math.abs(fromIdx), 0)) : parallel(arrOrStr).then(arr => arr.includes(searchItem, fromIdx)); }); }; /** * resolves to the value of the first element in the array that satisfies the provided callback function; otherwise, `undefined` * * *Note*: since `findAF` is run in parallel, `callback` will be run on all elements even if one of the first few elements passes the test; if this behavior is not desireable, consider using `series.findAF` or its alias, `io.findAF` * * @param {callback} callback function to test each element in the array * * `callback` accepts three arguments: * - `currentValue` value of the current element being processed in the array * - `index`*`(optional)`* index of `currentValue` in the array * - `array`*`(optional)`* the array that findAF is being applied to * @param {Object=} thisArg value to use as `this` when executing `callback` * @returns {Promise.<any>} `Promise` that resolves to the first element in the array that passes the test; otherwise, undefined * @example * * const inventory = [ * {name: 'nuts', quantity: 2000}, * {name: 'bolts', quantity: 5000}, * {name: 'screws', quantity: 9001} * ].map(part => Promise.resolve(part)); * * AsyncAF(inventory).findAF(part => part.name === 'screws'); * // Promise that resolves to {name: 'screws', quantity: 9001} * @since 3.5.0 * @see find (alias) * @see {@link AsyncAF#series series.findAF} * @memberof AsyncAF# */ const findAF = function findAF(callback$$1) { let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; return this.then(arr => { if (!permissiveIsArrayLike(arr)) throw TypeError("findAF cannot be called on ".concat(arr, ", only on an Array or array-like Object")); if (typeof callback$$1 !== 'function') throw TypeError("".concat(callback$$1, " is not a function")); const filled = Array.from(arr); const length = filled.length >>> 0; return this.inSeries ? (length || undefined) && function seriesFindAF(arr, i) { return Promise.resolve(arr[i]).then(el => { arr[i] = el; return Promise.resolve(callback$$1.call(thisArg, el, i, arr)).then(bool => { if (bool) return el; if (i === length - 1) return; return seriesFindAF(arr, i + 1); }); }); }(filled, 0) : parallel(filled, callback$$1, thisArg).then(bools => arr[bools.indexOf(true)]); }); }; /** * resolves to the index of the first element in the array that satisfies the provided callback function; otherwise, `-1` * * *Note*: since `findIndexAF` is run in parallel, `callback` will be run on all indices even if one of the first few indices passes the test; if this behavior is not desireable, consider using `series.findIndexAF` or its alias, `io.findIndexAF` * * @param {callback} callback function to test each element in the array * * `callback` accepts three arguments: * - `currentValue` value of the current element being processed in the array * - `index`*`(optional)`* index of `currentValue` in the array * - `array`*`(optional)`* the array that findIndexAF is being applied to * @param {Object=} thisArg value to use as `this` when executing `callback` * @returns {Promise.<Number>} `Promise` that resolves to the index of the first element in the array that passes the test; otherwise, `-1` * @example * * const inventory = [ * {name: 'nuts', quantity: 2000}, * {name: 'bolts', quantity: 5000}, * {name: 'screws', quantity: 9001} * ].map(part => Promise.resolve(part)); * * AsyncAF(inventory).findIndexAF(part => part.name === 'screws'); * // Promise that resolves to 2 * @since 3.5.0 * @see findIndex (alias) * @see {@link AsyncAF#series series.findIndexAF} * @memberof AsyncAF# */ const findIndexAF = function findIndexAF(callback$$1) { let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; return this.then(arr => { if (!permissiveIsArrayLike(arr)) throw TypeError("findIndexAF cannot be called on ".concat(arr, ", only on an Array or array-like Object")); if (typeof callback$$1 !== 'function') throw TypeError("".concat(callback$$1, " is not a function")); const filled = Array.from(arr); const length = filled.length >>> 0; return this.inSeries ? !length && -1 || function seriesFindIndexAF(arr, i) { return Promise.resolve(arr[i]).then(el => { arr[i] = el; return Promise.resolve(callback$$1.call(thisArg, el, i, arr)).then(bool => { if (bool) return i; if (i === length - 1) return -1; return seriesFindIndexAF(arr, i + 1); }); }); }(filled, 0) : parallel(filled, callback$$1, thisArg).then(bools => bools.indexOf(true)); }); }; /** * resolves to the first index of the specified element or string in an array, string, or array-like object, starting the search at `fromIndex`; if the value is not found, resolves to `-1` * * *Note*: when called on an array or array-like object, `indexOfAF` is run in parallel and all elements will be resolved even if one of the first few indices is a match; if this behavior is not desireable, consider using `series.indexOfAF` or its alias, `io.indexOfAF` * * @param {any} searchItem the element or string to search for * @param {Number=} fromIndex the index at which to begin searching for `searchItem`; a negative value searches from the index of `array/string.length - fromIndex`; defaults to `0` * @returns {Promise.<Number>} `Promise` that resolves to the index of `searchItem` if found; otherwise, `-1` * @example * * // indexOfAF on an array of promises * const nums = [1, 2, 3].map(n => Promise.resolve(n)); * * AsyncAF(nums).indexOfAF(2); // Promise that resolves to 1 * * AsyncAF(nums).indexOfAF(5); // Promise that resolves to -1 * * AsyncAF(nums).indexOfAF(1, 1); // Promise that resolves to -1 * * AsyncAF(nums).indexOfAF(3, -1); // Promise that resolves to 2 * * // indexOfAF on a promise-wrapped string * const string = Promise.resolve('test string'); * * AsyncAF(string).indexOfAF('test'); // Promise that resolves to 0 * * AsyncAF(string).indexOfAF('nope'); // Promise that resolves to -1 * * AsyncAF(string).indexOfAF('test', 5); // Promise that resolves to -1 * * AsyncAF(string).indexOfAF('string', -6); // Promise that resolves to 5 * * // indexOfAF on an array-like object * (async function () { * if (await AsyncAF(arguments).indexOfAF(2) > -1) { * console.log('2 is included'); * } * })(1, 2, 3); // logs '2 is included' * * @since 3.5.0 * @see indexOf (alias) * @see {@link AsyncAF#series series.indexOfAF} * @memberof AsyncAF# */ const indexOfAF = function indexOfAF(searchItem) { let fromIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; return this.then(arrOrStr => { if (!permissiveIsArrayLike(arrOrStr)) throw TypeError("indexOfAF cannot be called on ".concat(arrOrStr, ", only on an Array, String, or array-like Object")); const length = arrOrStr.length >>> 0; const fromIdx = fromIndex | 0; return typeof arrOrStr === 'string' ? arrOrStr.indexOf(searchItem, fromIdx) : this.inSeries ? function seriesIndexOfAF(i) { return Promise.resolve(arrOrStr[i]).then(el => { if (i in arrOrStr && el === searchItem) return i; if (i >= length - 1) return -1; return seriesIndexOfAF(i + 1); }); }(Math.max(fromIdx >= 0 ? fromIdx : length - Math.abs(fromIdx), 0)) : parallel(arrOrStr).then(arr => arr.indexOf(searchItem, fromIdx)); }); }; /** * resolves to the last index of the specified element or string, searching backwards in an array, string, or array-like object; `fromIndex` offsets the start of the search; if the value is not found, resolves to `-1` * * *Note*: when called on an array or array-like object, `lastIndexOfAF` is run in parallel and all elements will be resolved even if one of the last few indices is a match; if this behavior is not desireable, consider using `series.lastIndexOfAF` or its alias, `io.lastIndexOfAF` * * @param {any} searchItem the element or string to search for * @param {Number=} fromIndex the index at which to begin searching backwards for `searchItem`; a negative value searches from the index of `array/string.length - fromIndex`; defaults to `array/string.length - 1` * @returns {Promise.<Number>} `Promise` that resolves to the last index of `searchItem` if found; otherwise, `-1` * @example * * // lastIndexOfAF on an array of promises * const nums = [1, 1, 2, 2, 3, 3].map(n => Promise.resolve(n)); * * AsyncAF(nums).lastIndexOfAF(2); // Promise that resolves to 3 * * AsyncAF(nums).lastIndexOfAF(5); // Promise that resolves to -1 * * AsyncAF(nums).lastIndexOfAF(2, -4); // Promise that resolves to 2 * * AsyncAF(nums).lastIndexOfAF(3, -3); // Promise that resolves to -1 * * // lastIndexOfAF on a promise-wrapped string * const string = Promise.resolve('test string to test'); * * AsyncAF(string).lastIndexOfAF('test'); // Promise that resolves to 15 * * AsyncAF(string).lastIndexOfAF('nope'); // Promise that resolves to -1 * * AsyncAF(string).lastIndexOfAF('test', -5); // Promise that resolves to 0 * * AsyncAF(string).lastIndexOfAF('to', -7); // Promise that resolves to -1 * * // lastIndexOfAF on an array-like object * (async function () { * const lastIndexOf2 = await AsyncAF(arguments).lastIndexOfAF(2); * console.log(`the last index of 2 in the arguments array-like object is ${lastIndexOf2}`) * })(1, 1, 2, 2, 3, 3); // the last index of 2 in the arguments array-like object is 3 * * @since 3.6.0 * @see lastIndexOf (alias) * @see {@link AsyncAF#series series.lastIndexOfAF} * @memberof AsyncAF# *