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
JavaScript
/*!
* 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**
*
* ● file (*default*)
*
* ```js
* logAF.options({ labelFormat: 'file' });
* logAF(promise, 2);
*
* // @filename.js:24:1:
* // 1 2
* // in 0.998 secs
* ```
* ● path
*
* ```js
* logAF.options({ labelFormat: 'path' });
* logAF(promise, 2);
*
* // @/Path/to/current/directory/filename.js:24:1:
* // 1 2
* // in 0.997 secs
* ```
* ● parent
*
* ```js
* logAF.options({ labelFormat: 'parent' });
* logAF(promise, 2);
*
* // @parentDirectory/filename.js:24:1:
* // 1 2
* // in 0.998 secs
* ```
* ● arrow
*
* ```js
* logAF.options({ labelFormat: 'arrow' });
* logAF(promise, 2);
*
* // ========================> 1 2
* // in 0.999 secs
* ```
*
* ● 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#
*