├── .eslintrc.js
├── .gitignore
├── .jsdoc.json
├── .npmignore
├── .travis.yml
├── README.md
├── index.d.ts
├── index.js
├── lib
├── instance-methods.js
└── static-methods.js
├── package-lock.json
├── package.json
└── test
└── index.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | commonjs: true,
4 | es6: true,
5 | node: true
6 | },
7 | parser: 'babel-eslint',
8 | extends: 'eslint:recommended',
9 | rules: {
10 | indent: [
11 | 'error',
12 | 2
13 | ],
14 | 'linebreak-style': [
15 | 'error',
16 | 'unix'
17 | ],
18 | quotes: [
19 | 'error',
20 | 'single'
21 | ],
22 | semi: [
23 | 'error',
24 | 'always'
25 | ]
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 |
38 | # Optional npm cache directory
39 | .npm
40 |
41 | # Optional eslint cache
42 | .eslintcache
43 |
44 | # Optional REPL history
45 | .node_repl_history
46 |
47 | # Output of 'npm pack'
48 | *.tgz
49 |
50 | # Yarn Integrity file
51 | .yarn-integrity
52 |
53 | # dotenv environment variables file
54 | .env
55 |
--------------------------------------------------------------------------------
/.jsdoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tags": {
3 | "allowUnknownTags": false
4 | },
5 | "plugins": [
6 | "plugins/markdown",
7 | "node_modules/jsdoc-strip-async-await"
8 | ],
9 | "opts": {
10 | "encoding": "utf8",
11 | "destination": "docs/",
12 | "verbose": true,
13 | "template": "node_modules/simple-jsdoc"
14 | }
15 | }
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Test folder
2 | test
3 |
4 | # Docs
5 | docs
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - 8
5 | notifications:
6 | email: false
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # p-iteration [](https://travis-ci.org/toniov/p-iteration) [](http://badge.fury.io/js/p-iteration)
2 |
3 |
4 | > Make array iteration easy when using async/await and promises
5 |
6 | - Same functionality as the ES5 Array iteration methods we all know
7 | - All the methods return a `Promise`, making them awaitable and thenable
8 | - Allow the usage of async functions as callback
9 | - Callbacks run concurrently
10 | - Lightweight (no prd dependencies)
11 |
12 |
13 | ## Install
14 |
15 | ```
16 | $ npm install --save p-iteration
17 | ```
18 |
19 |
20 | ## Usage
21 |
22 | Smooth asynchronous iteration using `async/await`:
23 |
24 | ```js
25 | const { map } = require('p-iteration');
26 |
27 | // map passing an async function as callback
28 | function getUsers (userIds) {
29 | return map(userIds, async userId => {
30 | const response = await fetch(`/api/users/${userId}`);
31 | return response.json();
32 | });
33 | }
34 |
35 | // map passing a non-async function as callback
36 | async function getRawResponses (userIds) {
37 | const responses = await map(userIds, userId => fetch(`/api/users/${userId}`));
38 | // ... do some stuff
39 | return responses;
40 | }
41 |
42 | // ...
43 | ```
44 |
45 | ```js
46 | const { filter } = require('p-iteration');
47 |
48 | async function getFilteredUsers (userIds, name) {
49 | const filteredUsers = await filter(userIds, async userId => {
50 | const response = await fetch(`/api/users/${userId}`);
51 | const user = await response.json();
52 | return user.name === name;
53 | });
54 | // ... do some stuff
55 | return filteredUsers;
56 | }
57 |
58 | // ...
59 | ```
60 |
61 | All methods return a Promise so they can just be used outside an async function just with plain Promises:
62 |
63 | ```js
64 | const { map } = require('p-iteration');
65 |
66 | map([123, 125, 156], (userId) => fetch(`/api/users/${userId}`))
67 | .then((result) => {
68 | // ...
69 | })
70 | .catch((error) => {
71 | // ...
72 | });
73 | ```
74 |
75 | If there is a Promise in the array, it will be unwrapped before calling the callback:
76 |
77 | ```js
78 | const { forEach } = require('p-iteration');
79 | const fetchJSON = require('nonexistent-module');
80 |
81 | function logUsers () {
82 | const users = [
83 | fetchJSON('/api/users/125'), // returns a Promise
84 | { userId: 123, name: 'Jolyne', age: 19 },
85 | { userId: 156, name: 'Caesar', age: 20 }
86 | ];
87 | return forEach(users, (user) => {
88 | console.log(user);
89 | });
90 | }
91 | ```
92 |
93 | ```js
94 | const { find } = require('p-iteration');
95 | const fetchJSON = require('nonexistent-module');
96 |
97 | function findUser (name) {
98 | const users = [
99 | fetchJSON('/api/users/125'), // returns a Promise
100 | { userId: 123, name: 'Jolyne', age: 19 },
101 | { userId: 156, name: 'Caesar', age: 20 }
102 | ];
103 | return find(users, (user) => user.name === name);
104 | }
105 | ```
106 |
107 | The callback will be invoked as soon as the Promise is unwrapped:
108 |
109 | ```js
110 | const { forEach } = require('p-iteration');
111 |
112 | // function that returns a Promise resolved after 'ms' passed
113 | const delay = (ms) => new Promise(resolve => setTimeout(() => resolve(ms), ms));
114 |
115 | // 100, 200, 300 and 500 will be logged in this order
116 | async function logNumbers () {
117 | const numbers = [
118 | delay(500),
119 | delay(200),
120 | delay(300),
121 | 100
122 | ];
123 | await forEach(numbers, (number) => {
124 | console.log(number);
125 | });
126 | }
127 | ```
128 |
129 | ## API
130 |
131 | The methods are implementations of the ES5 Array iteration methods we all know with the same syntax, but all return a `Promise`. Also, with the exception of `reduce()`, all methods callbacks are run concurrently. There is a series version of each method, called: `${methodName}Series`, series methods use the same API that their respective concurrent ones.
132 |
133 | There is a link to the [original reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) of each method in the docs of this module:
134 |
135 | - [`forEach`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#forEach)
136 |
137 | - [`forEachSeries`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#forEachSeries)
138 |
139 | - [`map`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#map)
140 |
141 | - [`mapSeries`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#mapSeries)
142 |
143 | - [`find`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#find)
144 |
145 | - [`findSeries`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#findSeries)
146 |
147 | - [`findIndex`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#findIndex)
148 |
149 | - [`findIndexSeries`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#findIndexSeries)
150 |
151 | - [`some`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#some)
152 |
153 | - [`someSeries`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#someSeries)
154 |
155 | - [`every`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#every)
156 |
157 | - [`everySeries`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#everySeries)
158 |
159 | - [`filter`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#filter)
160 |
161 | - [`filterSeries`(array, callback, [thisArg])](https://toniov.github.io/p-iteration/global.html#filterSeries)
162 |
163 | - [`reduce`(array, callback, [initialValue])](https://toniov.github.io/p-iteration/global.html#reduce)
164 |
165 |
166 | ## Instance methods
167 |
168 | Extending native objects is discouraged and I don't recommend it, but in case you know what you are doing, you can extend `Array.prototype` to use the above methods as instance methods. They have been renamed as `async${MethodName}`, so the original ones are not overwritten.
169 |
170 | ```js
171 | const { instanceMethods } = require('p-iteration');
172 |
173 | Object.assign(Array.prototype, instanceMethods);
174 |
175 | async function example () {
176 | const foo = await [1, 2, 3].asyncMap((id) => fetch(`/api/example/${id}`));
177 | }
178 | ```
179 |
180 |
181 | ## License
182 |
183 | MIT © [Antonio V](https://github.com/toniov)
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "p-iteration" {
2 | /**
3 | * Implements ES5 [`Array#forEach()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) method.
4 | * Executes the provided callback once for each element.
5 | * Callbacks are run concurrently,
6 | * and are only invoked for properties of the array that have been initialized (including those initialized with *undefined*), for unassigned ones `callback` is not run.
7 | * @param {Array} array - Array to iterate over.
8 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
9 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
10 | * @return {Promise} - Returns a Promise with undefined value.
11 | */
12 | export const forEach: (
13 | array: T[],
14 | callback: (currentValue: T, index: number, array: T[]) => void,
15 | thisArg?: any
16 | ) => Promise;
17 |
18 | /**
19 | * Same functionality as [`forEach()`](global.html#forEach), but runs only one callback at a time.
20 | * @param {Array} array - Array to iterate over.
21 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
22 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
23 | * @return {Promise} - Returns a Promise with undefined value.
24 | */
25 | export const forEachSeries: (
26 | array: T[],
27 | callback: (currentValue: T, index: number, array: T[]) => void,
28 | thisArg?: any
29 | ) => Promise;
30 |
31 | /**
32 | * Implements ES5 [`Array#map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) method.
33 | * Creates a new array with the results of calling the provided callback once for each element.
34 | * Callbacks are run concurrently,
35 | * and are only invoked for properties of the array that have been initialized (including those initialized with *undefined*), for unassigned ones`callback` is not run.
36 | * Resultant *Array* is always the same *length* as the original one.
37 | * @param {Array} array - Array to iterate over.
38 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
39 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
40 | * @return {Promise} - Returns a Promise with the resultant *Array* as value.
41 | */
42 | export const map: (
43 | array: T[],
44 | callback: (currentValue: T, index: number, array: T[]) => U | Promise,
45 | thisArg?: any
46 | ) => Promise;
47 |
48 | /**
49 | * Same functionality as [`map()`](global.html#map), but runs only one callback at a time.
50 | * @param {Array} array - Array to iterate over.
51 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
52 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
53 | * @return {Promise} - Returns a Promise with the resultant *Array* as value.
54 | */
55 | export const mapSeries: (
56 | array: T[],
57 | callback: (currentValue: T, index: number, array: T[]) => U | Promise,
58 | thisArg?: any
59 | ) => Promise;
60 |
61 | /**
62 | * Implements ES5 [`Array#find()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) method.
63 | * Returns the value of the element that satisfies the provided `callback`. The value returned is the one found first.
64 | * Callbacks are run concurrently, meaning that all the callbacks are going to run even if the returned value is found in one of the first elements of `array`,
65 | * depending on the async calls you are going to use, consider using instead [`findSeries()`](global.html#findSeries).
66 | * @param {Array} array - Array to iterate over.
67 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
68 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
69 | * @return {Promise} - Returns a Promise with the element that passed the test as value, otherwise *undefined*.
70 | */
71 | export const find: (
72 | array: T[],
73 | callback: (currentValue: T, index: number, array: T[]) => boolean | Promise,
74 | thisArg?: any
75 | ) => Promise;
76 |
77 | /**
78 | * Same functionality as [`find()`](global.html#find), but runs only one callback at a time.
79 | * @param {Array} array - Array to iterate over.
80 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
81 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
82 | * @return {Promise} - Returns a Promise with the element that passed the test as value, otherwise *undefined*.
83 | */
84 | export const findSeries: (
85 | array: T[],
86 | callback: (currentValue: T, index: number, array: T[]) => boolean | Promise,
87 | thisArg?: any
88 | ) => Promise;
89 |
90 | /**
91 | * Implements ES5 [`Array#findIndex()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) method.
92 | * Returns the index of the element that satisfies the provided `callback`. The index returned is the one found first.
93 | * Callbacks are run concurrently, meaning that all the callbacks are going to run even if the returned index is found in one of the first elements of `array`,
94 | * depending on the async calls you are going to use, consider using instead [`findSeries()`](global.html#findSeries).
95 | * @param {Array} array - Array to iterate over.
96 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
97 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
98 | * @return {Promise} - Returns a Promise with the index that passed the test as value, otherwise *-1*.
99 | */
100 | export const findIndex: (
101 | array: T[],
102 | callback: (currentValue: T, index: number, array: T[]) => boolean | Promise,
103 | thisArg?: any
104 | ) => Promise;
105 |
106 | /**
107 | * Same functionality as [`findIndex()`](global.html#findIndex), but runs only one callback at a time.
108 | * @param {Array} array - Array to iterate over.
109 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
110 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
111 | * @return {Promise} - Returns a Promise with the index that passed the test, otherwise *-1*.
112 | */
113 | export const findIndexSeries: (
114 | array: T[],
115 | callback: (currentValue: T, index: number, array: T[]) => boolean | Promise,
116 | thisArg?: any
117 | ) => Promise;
118 |
119 | /**
120 | * Implements ES5 [`Array#some()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) method.
121 | * Test if some element in `array` passes the test implemented in `callback`.
122 | * Callbacks are run concurrently, meaning that all the callbacks are going to run even if some of the first elements pass the test,
123 | * depending on the async calls you are going to use, consider using instead [`someSeries()`](global.html#someSeries).
124 | * @param {Array} array - Array to iterate over.
125 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
126 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
127 | * @return {Promise} - Returns a Promise with *true* as value if some element passed the test, otherwise *false*.
128 | */
129 | export const some: (
130 | array: T[],
131 | callback: (currentValue: T, index: number, array: T[]) => boolean | Promise,
132 | thisArg?: any
133 | ) => Promise;
134 |
135 | /**
136 | * Same functionality as [`some()`](global.html#some), but runs only one callback at a time.
137 | * @param {Array} array - Array to iterate over.
138 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
139 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
140 | * @return {Promise} - Returns a Promise with *true* as value if some element passed the test, otherwise *false*.
141 | */
142 | export const someSeries: (
143 | array: T[],
144 | callback: (currentValue: T, index: number, array: T[]) => boolean | Promise,
145 | thisArg?: any
146 | ) => Promise;
147 |
148 | /**
149 | * Implements ES5 [`Array#every()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) method.
150 | * Test if all elements in `array` pass the test implemented in `callback`.
151 | * Callbacks are run concurrently, meaning that all the callbacks are going to run even if any of the first elements do not pass the test,
152 | * depending on the async calls you are going to use, consider using instead [`everySeries()`](global.html#everySeries).
153 | * @param {Array} array - Array to iterate over.
154 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
155 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
156 | * @return {Promise} - Returns a Promise with *true* as value if all elements passed the test, otherwise *false*.
157 | */
158 | export const every: (
159 | array: T[],
160 | callback: (currentValue: T, index: number, array: T[]) => boolean | Promise,
161 | thisArg?: any
162 | ) => Promise;
163 |
164 | /**
165 | * Same functionality as [`every()`](global.html#every), but runs only one callback at a time.
166 | * @param {Array} array - Array to iterate over.
167 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
168 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
169 | * @return {Promise} - Returns a Promise with *true* as value if all elements passed the test, otherwise *false*.
170 | */
171 | export const everySeries: (
172 | array: T[],
173 | callback: (currentValue: T, index: number, array: T[]) => boolean | Promise,
174 | thisArg?: any
175 | ) => Promise;
176 |
177 | /**
178 | * Implements ES5 [`Array#filter()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) method.
179 | * Creates a new array with the elements that passed the test implemented in `callback`.
180 | * Callbacks are run concurrently.
181 | * @param {Array} array - Array to iterate over.
182 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
183 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
184 | * @return {Promise} - Returns a Promise with the resultant filtered *Array* as value.
185 | */
186 | export const filter: (
187 | array: T[],
188 | callback: (currentValue: T, index: number, array: T[]) => boolean | Promise,
189 | thisArg?: any
190 | ) => Promise;
191 |
192 | /**
193 | * Same functionality as [`filter()`](global.html#filter), but runs only one callback at a time.
194 | * @param {Array} array - Array to iterate over.
195 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
196 | * @return {Promise} - Returns a Promise with the resultant filtered *Array* as value.
197 | */
198 | export const filterSeries: (
199 | array: T[],
200 | callback: (currentValue: T, index: number, array: T[]) => boolean | Promise,
201 | thisArg?: any
202 | ) => Promise;
203 |
204 | /**
205 | * Implements ES5 [`Array#reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) method.
206 | * Applies a `callback` against an accumulator and each element in `array`.
207 | * @param {Array} array - Array to iterate over.
208 | * @param {Function} callback - Function to apply each item in `array`. Accepts four arguments: `accumulator`, `currentValue`, `currentIndex` and `array`.
209 | * @param {Object} [initialValue] - Used as first argument to the first call of `callback`.
210 | * @return {Promise} - Returns a Promise with the resultant value from the reduction.
211 | */
212 | export const reduce: (
213 | array: T[],
214 | callback: (accumulator: U, currentValue: T, currentIndex: number, array: T[]) => U | Promise,
215 | initialValue?: U
216 | ) => Promise;
217 | }
218 |
219 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const staticMethods = require('./lib/static-methods');
2 | const instanceMethods = require('./lib/instance-methods');
3 |
4 | module.exports = Object.assign(staticMethods, { instanceMethods });
5 |
--------------------------------------------------------------------------------
/lib/instance-methods.js:
--------------------------------------------------------------------------------
1 | const staticMethods = require('./static-methods');
2 |
3 | Object.keys(staticMethods).forEach((methodName) => {
4 | const instaceMethodName = methodName.charAt(0).toUpperCase() + methodName.slice(1);
5 | exports[`async${instaceMethodName}`] = async function (...args) {
6 | return staticMethods[methodName](this, ...args);
7 | };
8 | });
9 |
--------------------------------------------------------------------------------
/lib/static-methods.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Implements ES5 [`Array#forEach()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) method.
3 | * Executes the provided callback once for each element.
4 | * Callbacks are run concurrently,
5 | * and are only invoked for properties of the array that have been initialized (including those initialized with *undefined*), for unassigned ones `callback` is not run.
6 | * @param {Array} array - Array to iterate over.
7 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
8 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
9 | * @return {Promise} - Returns a Promise with undefined value.
10 | */
11 | exports.forEach = async (array, callback, thisArg) => {
12 | const promiseArray = [];
13 | for (let i = 0; i < array.length; i++) {
14 | if (i in array) {
15 | const p = Promise.resolve(array[i]).then((currentValue) => {
16 | return callback.call(thisArg || this, currentValue, i, array);
17 | });
18 | promiseArray.push(p);
19 | }
20 | }
21 | await Promise.all(promiseArray);
22 | };
23 |
24 | /**
25 | * Same functionality as [`forEach()`](global.html#forEach), but runs only one callback at a time.
26 | * @param {Array} array - Array to iterate over.
27 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
28 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
29 | * @return {Promise} - Returns a Promise with undefined value.
30 | */
31 | exports.forEachSeries = async (array, callback, thisArg) => {
32 | for (let i = 0; i < array.length; i++) {
33 | if (i in array) {
34 | await callback.call(thisArg || this, await array[i], i, array);
35 | }
36 | }
37 | };
38 |
39 | /**
40 | * Implements ES5 [`Array#map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) method.
41 | * Creates a new array with the results of calling the provided callback once for each element.
42 | * Callbacks are run concurrently,
43 | * and are only invoked for properties of the array that have been initialized (including those initialized with *undefined*), for unassigned ones`callback` is not run.
44 | * Resultant *Array* is always the same *length* as the original one.
45 | * @param {Array} array - Array to iterate over.
46 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
47 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
48 | * @return {Promise} - Returns a Promise with the resultant *Array* as value.
49 | */
50 | exports.map = async (array, callback, thisArg) => {
51 | const promiseArray = [];
52 | for (let i = 0; i < array.length; i++) {
53 | if (i in array) {
54 | promiseArray[i] = Promise.resolve(array[i]).then((currentValue) => {
55 | return callback.call(thisArg || this, currentValue, i, array);
56 | });
57 | }
58 | }
59 | return Promise.all(promiseArray);
60 | };
61 |
62 | /**
63 | * Same functionality as [`map()`](global.html#map), but runs only one callback at a time.
64 | * @param {Array} array - Array to iterate over.
65 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
66 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
67 | * @return {Promise} - Returns a Promise with the resultant *Array* as value.
68 | */
69 | exports.mapSeries = async (array, callback, thisArg) => {
70 | const result = [];
71 | for (let i = 0; i < array.length; i++) {
72 | if (i in array) {
73 | result[i] = await callback.call(thisArg || this, await array[i], i, array);
74 | }
75 | }
76 | return result;
77 | };
78 |
79 | /**
80 | * Implements ES5 [`Array#find()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) method.
81 | * Returns the value of the element that satisfies the provided `callback`. The value returned is the one found first.
82 | * Callbacks are run concurrently, meaning that all the callbacks are going to run even if the returned value is found in one of the first elements of `array`,
83 | * depending on the async calls you are going to use, consider using instead [`findSeries()`](global.html#findSeries).
84 | * @param {Array} array - Array to iterate over.
85 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
86 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
87 | * @return {Promise} - Returns a Promise with the element that passed the test as value, otherwise *undefined*.
88 | */
89 | exports.find = (array, callback, thisArg) => {
90 | return new Promise((resolve, reject) => {
91 | if (array.length === 0) {
92 | return resolve();
93 | }
94 | let counter = 1;
95 | for (let i = 0; i < array.length; i++) {
96 | const check = (found) => {
97 | if (found) {
98 | resolve(array[i]);
99 | } else if (counter === array.length) {
100 | resolve();
101 | }
102 | counter++;
103 | };
104 | Promise.resolve(array[i])
105 | .then((elem) => callback.call(thisArg || this, elem, i, array))
106 | .then(check)
107 | .catch(reject);
108 | }
109 | });
110 | };
111 |
112 | /**
113 | * Same functionality as [`find()`](global.html#find), but runs only one callback at a time.
114 | * @param {Array} array - Array to iterate over.
115 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
116 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
117 | * @return {Promise} - Returns a Promise with the element that passed the test as value, otherwise *undefined*.
118 | */
119 | exports.findSeries = async (array, callback, thisArg) => {
120 | for (let i = 0; i < array.length; i++) {
121 | if (await callback.call(thisArg || this, await array[i], i, array)) {
122 | return array[i];
123 | }
124 | }
125 | };
126 |
127 | /**
128 | * Implements ES5 [`Array#findIndex()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) method.
129 | * Returns the index of the element that satisfies the provided `callback`. The index returned is the one found first.
130 | * Callbacks are run concurrently, meaning that all the callbacks are going to run even if the returned index is found in one of the first elements of `array`,
131 | * depending on the async calls you are going to use, consider using instead [`findSeries()`](global.html#findSeries).
132 | * @param {Array} array - Array to iterate over.
133 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
134 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
135 | * @return {Promise} - Returns a Promise with the index that passed the test as value, otherwise *-1*.
136 | */
137 | exports.findIndex = (array, callback, thisArg) => {
138 | return new Promise((resolve, reject) => {
139 | if (array.length === 0) {
140 | return resolve(-1);
141 | }
142 | let counter = 1;
143 | for (let i = 0; i < array.length; i++) {
144 | const check = (found) => {
145 | if (found) {
146 | resolve(i);
147 | } else if (counter === array.length) {
148 | resolve(-1);
149 | }
150 | counter++;
151 | };
152 | Promise.resolve(array[i])
153 | .then((elem) => callback.call(thisArg || this, elem, i, array))
154 | .then(check)
155 | .catch(reject);
156 | }
157 | });
158 | };
159 |
160 | /**
161 | * Same functionality as [`findIndex()`](global.html#findIndex), but runs only one callback at a time.
162 | * @param {Array} array - Array to iterate over.
163 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
164 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
165 | * @return {Promise} - Returns a Promise with the index that passed the test, otherwise *-1*.
166 | */
167 | exports.findIndexSeries = async (array, callback, thisArg) => {
168 | for (let i = 0; i < array.length; i++) {
169 | if (await callback.call(thisArg || this, await array[i], i, array)) {
170 | return i;
171 | }
172 | }
173 | };
174 |
175 | /**
176 | * Implements ES5 [`Array#some()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) method.
177 | * Test if some element in `array` passes the test implemented in `callback`.
178 | * Callbacks are run concurrently, meaning that all the callbacks are going to run even if some of the first elements pass the test,
179 | * depending on the async calls you are going to use, consider using instead [`someSeries()`](global.html#someSeries).
180 | * @param {Array} array - Array to iterate over.
181 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
182 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
183 | * @return {Promise} - Returns a Promise with *true* as value if some element passed the test, otherwise *false*.
184 | */
185 | exports.some = (array, callback, thisArg) => {
186 | return new Promise((resolve, reject) => {
187 | if (array.length === 0) {
188 | return resolve(false);
189 | }
190 | let counter = 1;
191 | for (let i = 0; i < array.length; i++) {
192 | if (!(i in array)) {
193 | counter++;
194 | continue;
195 | }
196 | const check = (found) => {
197 | if (found) {
198 | resolve(true);
199 | } else if (counter === array.length) {
200 | resolve(false);
201 | }
202 | counter++;
203 | };
204 | Promise.resolve(array[i])
205 | .then((elem) => callback.call(thisArg || this, elem, i, array))
206 | .then(check)
207 | .catch(reject);
208 | }
209 | });
210 | };
211 |
212 | /**
213 | * Same functionality as [`some()`](global.html#some), but runs only one callback at a time.
214 | * @param {Array} array - Array to iterate over.
215 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
216 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
217 | * @return {Promise} - Returns a Promise with *true* as value if some element passed the test, otherwise *false*.
218 | */
219 | exports.someSeries = async (array, callback, thisArg) => {
220 | for (let i = 0; i < array.length; i++) {
221 | if (i in array && await callback.call(thisArg || this, await array[i], i, array)) {
222 | return true;
223 | }
224 | }
225 | return false;
226 | };
227 |
228 | /**
229 | * Implements ES5 [`Array#every()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) method.
230 | * Test if all elements in `array` pass the test implemented in `callback`.
231 | * Callbacks are run concurrently, meaning that all the callbacks are going to run even if any of the first elements do not pass the test,
232 | * depending on the async calls you are going to use, consider using instead [`everySeries()`](global.html#everySeries).
233 | * @param {Array} array - Array to iterate over.
234 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
235 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
236 | * @return {Promise} - Returns a Promise with *true* as value if all elements passed the test, otherwise *false*.
237 | */
238 | exports.every = (array, callback, thisArg) => {
239 | return new Promise((resolve, reject) => {
240 | if (array.length === 0) {
241 | return resolve(true);
242 | }
243 | let counter = 1;
244 | for (let i = 0; i < array.length; i++) {
245 | if (!(i in array)) {
246 | counter++;
247 | continue;
248 | }
249 | const check = (found) => {
250 | if (!found) {
251 | resolve(false);
252 | } else if (counter === array.length) {
253 | resolve(true);
254 | }
255 | counter++;
256 | };
257 | Promise.resolve(array[i])
258 | .then((elem) => callback.call(thisArg || this, elem, i, array))
259 | .then(check)
260 | .catch(reject);
261 | }
262 | });
263 | };
264 |
265 | /**
266 | * Same functionality as [`every()`](global.html#every), but runs only one callback at a time.
267 | * @param {Array} array - Array to iterate over.
268 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
269 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
270 | * @return {Promise} - Returns a Promise with *true* as value if all elements passed the test, otherwise *false*.
271 | */
272 | exports.everySeries = async (array, callback, thisArg) => {
273 | for (let i = 0; i < array.length; i++) {
274 | if (i in array && !await callback.call(thisArg || this, await array[i], i, array)) {
275 | return false;
276 | }
277 | }
278 | return true;
279 | };
280 |
281 | /**
282 | * Implements ES5 [`Array#filter()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) method.
283 | * Creates a new array with the elements that passed the test implemented in `callback`.
284 | * Callbacks are run concurrently.
285 | * @param {Array} array - Array to iterate over.
286 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
287 | * @param {Object} [thisArg] - Value to use as *this* when executing the `callback`.
288 | * @return {Promise} - Returns a Promise with the resultant filtered *Array* as value.
289 | */
290 | exports.filter = (array, callback, thisArg) => {
291 | /* two loops are necessary in order to do the filtering concurrently
292 | * while keeping the order of the elements
293 | * (if you find a better way to do it please send a PR!)
294 | */
295 | return new Promise(async (resolve, reject) => {
296 | const promiseArray = [];
297 | for (let i = 0; i < array.length; i++) {
298 | if (i in array) {
299 | promiseArray[i] = Promise.resolve(array[i]).then((currentValue) => {
300 | return callback.call(thisArg || this, currentValue, i, array);
301 | }).catch(reject);
302 | }
303 | }
304 | const filteredArray = [];
305 | for (let i = 0; i < promiseArray.length; i++) {
306 | if (await promiseArray[i]) {
307 | filteredArray.push(await array[i]);
308 | }
309 | }
310 | resolve(filteredArray);
311 | });
312 | };
313 |
314 | /**
315 | * Same functionality as [`filter()`](global.html#filter), but runs only one callback at a time.
316 | * @param {Array} array - Array to iterate over.
317 | * @param {Function} callback - Function to apply each item in `array`. Accepts three arguments: `currentValue`, `index` and `array`.
318 | * @return {Promise} - Returns a Promise with the resultant filtered *Array* as value.
319 | */
320 | exports.filterSeries = async (array, callback, thisArg) => {
321 | const result = [];
322 | for (let i = 0; i < array.length; i++) {
323 | if (i in array && await callback.call(thisArg || this, await array[i], i, array)) {
324 | result.push(await array[i]);
325 | }
326 | }
327 | return result;
328 | };
329 |
330 | /**
331 | * Implements ES5 [`Array#reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) method.
332 | * Applies a `callback` against an accumulator and each element in `array`.
333 | * @param {Array} array - Array to iterate over.
334 | * @param {Function} callback - Function to apply each item in `array`. Accepts four arguments: `accumulator`, `currentValue`, `currentIndex` and `array`.
335 | * @param {Object} [initialValue] - Used as first argument to the first call of `callback`.
336 | * @return {Promise} - Returns a Promise with the resultant value from the reduction.
337 | */
338 | exports.reduce = async (array, callback, initialValue) => {
339 | if (array.length === 0 && initialValue === undefined) {
340 | throw TypeError('Reduce of empty array with no initial value');
341 | }
342 | let i;
343 | let previousValue;
344 | if (initialValue !== undefined) {
345 | previousValue = initialValue;
346 | i = 0;
347 | } else {
348 | previousValue = array[0];
349 | i = 1;
350 | }
351 | for (i; i < array.length; i++) {
352 | if (i in array) {
353 | previousValue = await callback(await previousValue, await array[i], i, array);
354 | }
355 | }
356 | return previousValue;
357 | };
358 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "p-iteration",
3 | "version": "1.1.7",
4 | "description": "Make array iteration easy when using async/await and Promises",
5 | "main": "index.js",
6 | "types": "index.d.ts",
7 | "scripts": {
8 | "test": "ava test/index.js",
9 | "lint": "eslint lib test",
10 | "generate-docs": "jsdoc -c .jsdoc.json -R README.md lib/static-methods.js"
11 | },
12 | "keywords": [
13 | "async/await",
14 | "async",
15 | "await",
16 | "asynchronous",
17 | "awaitable",
18 | "async function",
19 | "array",
20 | "iteration",
21 | "forEach",
22 | "map",
23 | "every",
24 | "some",
25 | "filter",
26 | "reduce"
27 | ],
28 | "author": "Antonio Valverde",
29 | "license": "MIT",
30 | "devDependencies": {
31 | "ava": "1.3.1",
32 | "babel-eslint": "7.2.3",
33 | "eslint": "4.0.0",
34 | "jsdoc": "3.4.3",
35 | "jsdoc-strip-async-await": "0.1.0",
36 | "simple-jsdoc": "0.0.4"
37 | },
38 | "engines": {
39 | "node": ">=8.0.0"
40 | },
41 | "browserify": {
42 | "transform": [
43 | [
44 | "babelify",
45 | {
46 | "presets": [
47 | "es2015",
48 | "es2016",
49 | "es2017"
50 | ]
51 | }
52 | ]
53 | ]
54 | },
55 | "repository": {
56 | "type": "git",
57 | "url": "https://github.com/toniov/p-iteration"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const test = require('ava');
4 | const { forEach, map, find, findIndex, some, every, filter, reduce } = require('../');
5 | const { forEachSeries, mapSeries, findSeries, findIndexSeries, someSeries, everySeries, filterSeries } = require('../');
6 | const { asyncForEach } = require('../').instanceMethods;
7 |
8 | const delay = (ms) => new Promise(resolve => setTimeout(() => resolve(ms), ms || 0));
9 |
10 | test('forEach, check callbacks are run in parallel', async (t) => {
11 | let total = 0;
12 | const parallelCheck = [];
13 | await forEach([2, 1, 3], async (num, index, array) => {
14 | await delay(num * 100);
15 | t.is(array[index], num);
16 | parallelCheck.push(num);
17 | total += num;
18 | });
19 | t.deepEqual(parallelCheck, [1, 2, 3]);
20 | t.is(total, 6);
21 | });
22 |
23 | test('forEach passing a non-async function', async (t) => {
24 | let total = 0;
25 | await forEach([2, 1, 3], (num, index, array) => {
26 | t.is(array[index], num);
27 | total += num;
28 | });
29 | t.is(total, 6);
30 | });
31 |
32 | test('forEach unwraps Promises in the array', async (t) => {
33 | let total = 0;
34 | await forEach([2, Promise.resolve(1), 3], async (num, index, array) => {
35 | t.is(await Promise.resolve(array[index]), num);
36 | total += num;
37 | });
38 | t.is(total, 6);
39 | });
40 |
41 | test('forEach should execute callbacks as soon as Promises are unwrapped', async (t) => {
42 | const parallelCheck = [];
43 | await forEach([delay(500), delay(300), delay(400)], (num) => {
44 | parallelCheck.push(num);
45 | });
46 | t.deepEqual(parallelCheck, [300, 400, 500]);
47 | });
48 |
49 | test('forEach, throw inside callback', async function (t) {
50 | const err = await t.throwsAsync(forEach([2, 1, 3], () => {
51 | throw new Error('test');
52 | }));
53 | t.is(err.message, 'test');
54 | });
55 |
56 | test('forEach using thisArg', async (t) => {
57 | let total = 0;
58 | const testObj = { test: 1 };
59 | await forEach([1, 2, 3], async function (num, index, array) {
60 | await delay();
61 | t.is(array[index], num);
62 | t.deepEqual(this, testObj);
63 | total += num;
64 | }, testObj);
65 | t.is(total, 6);
66 | });
67 |
68 | test.cb('forEach used with promises in a non-async function', (t) => {
69 | let total = 0;
70 | forEach([1, 2, 3], async function (num, index, array) {
71 | await delay();
72 | t.is(array[index], num);
73 | total += num;
74 | }).then(() => {
75 | t.is(total, 6);
76 | t.end();
77 | });
78 | });
79 |
80 | test('forEach should not execute any callback if array is empty', async (t) => {
81 | let count = 0;
82 | await forEach([], async () => {
83 | await delay();
84 | count++;
85 | });
86 | t.is(count, 0);
87 | });
88 |
89 | test('forEach should skip holes in arrays', async (t) => {
90 | let count = 0;
91 | // eslint-disable-next-line no-sparse-arrays
92 | await forEach([0, 1, 2, , 5, ,], async () => {
93 | count++;
94 | });
95 | t.is(count, 4);
96 | });
97 |
98 | test('map, check callbacks are run in parallel', async (t) => {
99 | const parallelCheck = [];
100 | const arr = await map([3, 1, 2], async (num, index, array) => {
101 | await delay(num * 100);
102 | t.is(array[index], num);
103 | parallelCheck.push(num);
104 | return num * 2;
105 | });
106 | t.deepEqual(arr, [6, 2, 4]);
107 | t.deepEqual(parallelCheck, [1, 2, 3]);
108 | });
109 |
110 | test('map unwraps Promises in the array', async (t) => {
111 | const arr = await map([2, Promise.resolve(1), 3], async (num, index, array) => {
112 | t.is(await Promise.resolve(array[index]), num);
113 | return num * 2;
114 | });
115 | t.deepEqual(arr, [4, 2, 6]);
116 | });
117 |
118 | test('map should execute callbacks as soon as Promises are unwrapped', async (t) => {
119 | const parallelCheck = [];
120 | await map([delay(500), delay(300), delay(400)], (num) => {
121 | parallelCheck.push(num);
122 | });
123 | t.deepEqual(parallelCheck, [300, 400, 500]);
124 | });
125 |
126 | test('map passing a non-async function that return a promise', async (t) => {
127 | const arr = await map([1, 2, 3], (num) => Promise.resolve(num * 2));
128 | t.deepEqual(arr, [2, 4, 6]);
129 | });
130 |
131 | test('map, throw inside callback', async function (t) {
132 | const err = await t.throwsAsync(map([2, 1, 3], () => {
133 | throw new Error('test');
134 | }));
135 | t.is(err.message, 'test');
136 | });
137 |
138 | test.cb('map used with promises in a non-async function', (t) => {
139 | map([1, 2, 3], async function (num) {
140 | await delay();
141 | return num * 2;
142 | }).then((result) => {
143 | t.deepEqual(result, [2, 4, 6]);
144 | t.end();
145 | });
146 | });
147 |
148 | test('map should return an empty array if passed array is empty', async (t) => {
149 | const count = 0;
150 | const arr = await map([], async () => {
151 | await delay();
152 | return 3;
153 | });
154 | t.deepEqual(arr, []);
155 | t.deepEqual(count, 0);
156 | });
157 |
158 | test('map should skip holes in arrays', async (t) => {
159 | let count = 0;
160 | // eslint-disable-next-line no-sparse-arrays
161 | await map([0, 1, 2, , 5, ,], async () => {
162 | count++;
163 | });
164 | t.is(count, 4);
165 | });
166 |
167 | test('find', async (t) => {
168 | const foundNum = await find([1, 2, 3], async (num, index, array) => {
169 | await delay();
170 | t.is(array[index], num);
171 | return num === 2;
172 | });
173 | t.is(foundNum, 2);
174 | });
175 |
176 | test('find, throw inside callback', async function (t) {
177 | const err = await t.throwsAsync(find([2, 1, 3], () => {
178 | throw new Error('test');
179 | }));
180 | t.is(err.message, 'test');
181 | });
182 |
183 | test('find returns undefined if did not find anything', async (t) => {
184 | const foundNum = await find([1, 2], async () => {
185 | await delay();
186 | return false;
187 | });
188 | t.is(foundNum, undefined);
189 | });
190 |
191 | test('find returns undefined if array is empty', async (t) => {
192 | const foundNum = await find([], async () => {
193 | await delay();
194 | return false;
195 | });
196 | t.is(foundNum, undefined);
197 | });
198 |
199 | test('find unwraps Promises in the array', async (t) => {
200 | const foundNum = await find([1, Promise.resolve(2), 3], async (num, index, array) => {
201 | await delay();
202 | t.is(await Promise.resolve(array[index]), num);
203 | return num === 2;
204 | });
205 | t.is(foundNum, 2);
206 | });
207 |
208 | test('find should execute callbacks as soon as Promises are unwrapped', async (t) => {
209 | const parallelCheck = [];
210 | await find([delay(500), delay(300), delay(400)], (num) => {
211 | parallelCheck.push(num);
212 | });
213 | t.deepEqual(parallelCheck, [300, 400, 500]);
214 | });
215 |
216 | test('find passing a non-async callback', async (t) => {
217 | const foundNum = await find([1, 2, 3], (num, index, array) => {
218 | t.is(array[index], num);
219 | return num === 2;
220 | });
221 | t.is(foundNum, 2);
222 | });
223 |
224 | test('find should not skip holes in arrays', async (t) => {
225 | let count = 0;
226 | // eslint-disable-next-line no-sparse-arrays
227 | await find([0, 1, 2, , 5, ,], async () => {
228 | count++;
229 | });
230 | t.is(count, 6);
231 | });
232 |
233 | test('findIndex', async (t) => {
234 | const foundIndex = await findIndex([1, 2, 3], async (num, index, array) => {
235 | await delay();
236 | t.is(array[index], num);
237 | return num === 2;
238 | });
239 | t.is(foundIndex, 1);
240 | });
241 |
242 | test('findIndex, throw inside callback', async function (t) {
243 | const err = await t.throwsAsync(findIndex([2, 1, 3], () => {
244 | throw new Error('test');
245 | }));
246 | t.is(err.message, 'test');
247 | });
248 |
249 | test('findIndex returns -1 if did not find anything', async (t) => {
250 | const notFound = await findIndex([1, 2], async () => {
251 | await delay();
252 | return false;
253 | });
254 | t.is(notFound, -1);
255 | });
256 |
257 | test('findIndex returns -1 if array is empty', async (t) => {
258 | const notFound = await findIndex([], async () => {
259 | await delay();
260 | return false;
261 | });
262 | t.is(notFound, -1);
263 | });
264 |
265 | test('findIndex unwraps Promises in the array', async (t) => {
266 | const foundIndex = await findIndex([Promise.resolve(1), 2, 3], async (num, index, array) => {
267 | await delay();
268 | t.is(await Promise.resolve(array[index]), num);
269 | return num === 2;
270 | });
271 | t.is(foundIndex, 1);
272 | });
273 |
274 | test('findIndex should execute callbacks as soon as Promises are unwrapped', async (t) => {
275 | const parallelCheck = [];
276 | await findIndex([delay(500), delay(300), delay(400)], (num) => {
277 | parallelCheck.push(num);
278 | });
279 | t.deepEqual(parallelCheck, [300, 400, 500]);
280 | });
281 |
282 | test('findIndex should not skip holes in arrays', async (t) => {
283 | let count = 0;
284 | // eslint-disable-next-line no-sparse-arrays
285 | await findIndex([0, 1, 2, , 5, ,], async () => {
286 | count++;
287 | });
288 | t.is(count, 6);
289 | });
290 |
291 | test('some', async (t) => {
292 | const isIncluded = await some([1, 2, 3], async (num, index, array) => {
293 | await delay();
294 | t.is(array[index], num);
295 | return num === 3;
296 | });
297 | t.true(isIncluded);
298 | });
299 |
300 | test('some, throw inside callback', async function (t) {
301 | const err = await t.throwsAsync(some([2, 1, 3], () => {
302 | throw new Error('test');
303 | }));
304 | t.is(err.message, 'test');
305 | });
306 |
307 | test('some should iterate until the end', async (t) => {
308 | const isIncluded = await some([500, 200, 400], async (num, index, array) => {
309 | await delay(num);
310 | t.is(array[index], num);
311 | return num === 500;
312 | });
313 | t.true(isIncluded);
314 | });
315 |
316 | test('some unwraps Promises in the array', async (t) => {
317 | const isIncluded = await some([1, Promise.resolve(2), 3], async (num, index, array) => {
318 | await delay();
319 | t.is(await Promise.resolve(array[index]), num);
320 | return num === 3;
321 | });
322 | t.true(isIncluded);
323 | });
324 |
325 | test('some should execute callbacks as soon as Promises are unwrapped', async (t) => {
326 | const parallelCheck = [];
327 | await some([delay(500), delay(300), delay(400)], (num) => {
328 | parallelCheck.push(num);
329 | });
330 | t.deepEqual(parallelCheck, [300, 400, 500]);
331 | });
332 |
333 | test('some passing a non-async callback', async (t) => {
334 | const isIncluded = await some([1, 2, 3], (num, index, array) => {
335 | t.is(array[index], num);
336 | return num === 3;
337 | });
338 | t.true(isIncluded);
339 | });
340 |
341 | test('some (return false)', async (t) => {
342 | const isIncluded = await some([1, 2, 3], async (num, index, array) => {
343 | await delay();
344 | t.is(array[index], num);
345 | return num === 4;
346 | });
347 | t.false(isIncluded);
348 | });
349 |
350 | test('some with empty array should return false', async (t) => {
351 | const isIncluded = await some([], async () => {
352 | await delay();
353 | return false;
354 | });
355 | t.false(isIncluded);
356 | });
357 |
358 | test('some should skip holes in arrays', async (t) => {
359 | let count = 0;
360 | // eslint-disable-next-line no-sparse-arrays
361 | await some([0, 1, 2, , 5, ,], async () => {
362 | count++;
363 | });
364 | t.is(count, 4);
365 | });
366 |
367 | test('every', async (t) => {
368 | const allIncluded = await every([1, 2, 3], async (num, index, array) => {
369 | await delay();
370 | t.is(array[index], num);
371 | return typeof num === 'number';
372 | });
373 | t.true(allIncluded);
374 | });
375 |
376 | test('every, throw inside callback', async function (t) {
377 | const err = await t.throwsAsync(every([2, 1, 3], () => {
378 | throw new Error('test');
379 | }));
380 | t.is(err.message, 'test');
381 | });
382 |
383 | test('every should iterate until the end', async (t) => {
384 | const isIncluded = await every([500, 200, 400], async (num, index, array) => {
385 | await delay(num);
386 | t.is(array[index], num);
387 | return true;
388 | });
389 | t.true(isIncluded);
390 | });
391 |
392 | test('every unwraps Promises in the array', async (t) => {
393 | const allIncluded = await every([Promise.resolve(1), 2, 3], async (num, index, array) => {
394 | await delay();
395 | t.is(await Promise.resolve(array[index]), num);
396 | return typeof num === 'number';
397 | });
398 | t.true(allIncluded);
399 | });
400 |
401 | test('every should execute callbacks as soon as Promises are unwrapped', async (t) => {
402 | const parallelCheck = [];
403 | await every([delay(500), delay(300), delay(400)], (num) => {
404 | parallelCheck.push(num);
405 | return true;
406 | });
407 | t.deepEqual(parallelCheck, [300, 400, 500]);
408 | });
409 |
410 | test('every passing a non-async callback', async (t) => {
411 | const allIncluded = await every([1, 2, 3], (num, index, array) => {
412 | t.is(array[index], num);
413 | return typeof num === 'number';
414 | });
415 | t.true(allIncluded);
416 | });
417 |
418 | test('every (return false)', async (t) => {
419 | const allIncluded = await every([1, 2, '3'], async (num, index, array) => {
420 | await delay();
421 | t.is(array[index], num);
422 | return typeof num === 'number';
423 | });
424 | t.false(allIncluded);
425 | });
426 |
427 | test('every with empty array should return true', async (t) => {
428 | const allIncluded = await every([], async () => {
429 | await delay();
430 | return false;
431 | });
432 | t.true(allIncluded);
433 | });
434 |
435 | test('every should skip holes in arrays', async (t) => {
436 | let count = 0;
437 | // eslint-disable-next-line no-sparse-arrays
438 | const allIncluded = await every([0, 1, 2, , 5, ,], async () => {
439 | count++;
440 | return true;
441 | });
442 | t.is(allIncluded, true);
443 | t.is(count, 4);
444 | });
445 |
446 | test('filter', async (t) => {
447 | const numbers = await filter([2, 1, '3', 4, '5'], async (num) => {
448 | await delay(num * 100);
449 | return typeof num === 'number';
450 | });
451 | t.deepEqual(numbers, [2, 1, 4]);
452 | });
453 |
454 | test('filter should skip holes in arrays', async (t) => {
455 | let count = 0;
456 | // eslint-disable-next-line no-sparse-arrays
457 | const numbers = await filter([0, 1, 2, '3', , 5, '6', ,], async (num) => {
458 | await delay(num * 100);
459 | count++;
460 | return typeof num === 'number';
461 | });
462 | t.is(count, 6);
463 | t.deepEqual(numbers, [0, 1, 2, 5]);
464 | });
465 |
466 | test('filter, check callbacks are run in parallel', async (t) => {
467 | const parallelCheck = [];
468 | const numbers = await filter([2, 1, '3'], async (num, index, array) => {
469 | await delay(num * 100);
470 | t.is(array[index], num);
471 | if (typeof num === 'number') {
472 | parallelCheck.push(num);
473 | return true;
474 | }
475 | });
476 | t.deepEqual(parallelCheck, [1, 2]);
477 | t.deepEqual(numbers, [2, 1]);
478 | });
479 |
480 | test('filter, throw inside callback', async function (t) {
481 | const err = await t.throwsAsync(filter([2, 1, 3], () => {
482 | throw new Error('test');
483 | }));
484 | t.is(err.message, 'test');
485 | });
486 |
487 | test('filter unwraps Promises in the array', async (t) => {
488 | const parallelCheck = [];
489 | const numbers = await filter([Promise.resolve(2), 1, '3'], async (num, index, array) => {
490 | await delay(num * 100);
491 | t.is(await Promise.resolve(array[index]), num);
492 | if (typeof num === 'number') {
493 | parallelCheck.push(num);
494 | return true;
495 | }
496 | });
497 | t.deepEqual(parallelCheck, [1, 2]);
498 | t.deepEqual(numbers, [2, 1]);
499 | });
500 |
501 | test('filter should execute callbacks as soon as Promises are unwrapped', async (t) => {
502 | const parallelCheck = [];
503 | await filter([delay(500), delay(300), delay(400)], (num) => {
504 | parallelCheck.push(num);
505 | });
506 | t.deepEqual(parallelCheck, [300, 400, 500]);
507 | });
508 |
509 | test('filter should return an empty array if passed array is empty', async (t) => {
510 | let count = 0;
511 | const empty = await filter([], async () => {
512 | await delay();
513 | count++;
514 | return true;
515 | });
516 | t.deepEqual(count, 0);
517 | t.deepEqual(empty, []);
518 | });
519 |
520 | test('reduce with initialValue', async (t) => {
521 | const sum = await reduce([1, 2, 3], async (accumulator, currentValue, index, array) => {
522 | await delay();
523 | t.is(array[index], currentValue);
524 | return accumulator + currentValue;
525 | }, 1);
526 | t.is(sum, 7);
527 | });
528 |
529 | test('reduce with falsy initialValue', async (t) => {
530 | const sum = await reduce(['1', '2', '3'], async (accumulator, currentValue, index, array) => {
531 | await delay();
532 | t.is(array[index], currentValue);
533 | return accumulator + Number(currentValue);
534 | }, 0);
535 | t.is(sum, 6);
536 |
537 | const string = await reduce([1, 2, 3], async (accumulator, currentValue, index, array) => {
538 | await delay();
539 | t.is(array[index], currentValue);
540 | return accumulator + String(currentValue);
541 | }, '');
542 | t.is(string, '123');
543 |
544 | const somePositive = await reduce([-1, 2, 3], async (accumulator, currentValue, index, array) => {
545 | await delay();
546 | t.is(array[index], currentValue);
547 | return accumulator ? accumulator : currentValue > 0;
548 | }, false);
549 | t.is(somePositive, true);
550 | });
551 |
552 | test('reduce, throw inside callback', async function (t) {
553 | const err = await t.throwsAsync(reduce([2, 1, 3], () => {
554 | throw new Error('test');
555 | }));
556 | t.is(err.message, 'test');
557 | });
558 |
559 | test('reduce unwrap Promises in the array', async (t) => {
560 | const sum = await reduce([Promise.resolve(1), 2, 3], async (accumulator, currentValue, index, array) => {
561 | await delay();
562 | t.is(await Promise.resolve(array[index]), currentValue);
563 | return accumulator + currentValue;
564 | }, 1);
565 | t.is(sum, 7);
566 | });
567 |
568 | test('reduce unwrap Promises in the initialValue', async (t) => {
569 | const sum = await reduce([1, 2, 3], async (accumulator, currentValue, index, array) => {
570 | await delay();
571 | t.is(await Promise.resolve(array[index]), currentValue);
572 | return accumulator + currentValue;
573 | }, Promise.resolve(1));
574 | t.is(sum, 7);
575 | });
576 |
577 | test('reduce without initialValue', async (t) => {
578 | const sum = await reduce([1, 2, 3], async (accumulator, currentValue, index, array) => {
579 | await delay();
580 | t.is(array[index], currentValue);
581 | return accumulator + currentValue;
582 | });
583 | t.is(sum, 6);
584 | });
585 |
586 | test('reduce of array with two elements without initialValue', async (t) => {
587 | const sum = await reduce([1, 2], async (accumulator, currentValue, index, array) => {
588 | await delay();
589 | t.is(array[index], currentValue);
590 | return accumulator + currentValue;
591 | });
592 | t.is(sum, 3);
593 | });
594 |
595 | test('reduce of empty array without initialValue should throw TypeError', async (t) => {
596 | const err = await t.throwsAsync(
597 | reduce([], async (accumulator, currentValue) => {
598 | await delay();
599 | return accumulator + currentValue;
600 | })
601 | );
602 | t.is(err.name, 'TypeError');
603 | t.is(err.message, 'Reduce of empty array with no initial value');
604 | });
605 |
606 | test('reduce of empty array with initialValue should return initialValue', async (t) => {
607 | let count = 0;
608 | const sum = await reduce([], async (accumulator, currentValue) => {
609 | await delay();
610 | count++;
611 | return accumulator + currentValue;
612 | }, 6);
613 | t.is(count, 0);
614 | t.is(sum, 6);
615 | });
616 |
617 | test('reduce of array with one element and no initialValue should return that element', async (t) => {
618 | let count = 0;
619 | const sum = await reduce([6], async (accumulator, currentValue) => {
620 | await delay();
621 | count++;
622 | return accumulator + currentValue;
623 | });
624 | t.is(count, 0);
625 | t.is(sum, 6);
626 | });
627 |
628 | test('forEachSeries', async (t) => {
629 | let total = 0;
630 | const seriesCheck = [];
631 | await forEachSeries([2, 1, 3], async (num, index, array) => {
632 | await delay(num * 100);
633 | t.is(array[index], num);
634 | seriesCheck.push(num);
635 | total += num;
636 | });
637 | t.deepEqual(seriesCheck, [2, 1, 3]);
638 | t.is(total, 6);
639 | });
640 |
641 | test('asyncForEach', async (t) => {
642 | let total = 0;
643 | await asyncForEach.call([2, 1, 3], async (num, index, array) => {
644 | await delay(num * 100);
645 | t.is(array[index], num);
646 | total += num;
647 | });
648 | t.is(total, 6);
649 | });
650 |
651 | test('mapSeries', async (t) => {
652 | const seriesCheck = [];
653 | const arr = await mapSeries([3, 1, 2], async (num, index, array) => {
654 | await delay(num * 100);
655 | t.is(array[index], num);
656 | seriesCheck.push(num);
657 | return num * 2;
658 | });
659 | t.deepEqual(arr, [6, 2, 4]);
660 | t.deepEqual(seriesCheck, [3, 1, 2]);
661 | });
662 |
663 | test('findSeries', async (t) => {
664 | const seriesCheck = [];
665 | const foundNum = await findSeries([3, 1, 2], async (num, index, array) => {
666 | await delay(num * 100);
667 | t.is(array[index], num);
668 | seriesCheck.push(num);
669 | return num === 2;
670 | });
671 | t.is(foundNum, 2);
672 | t.deepEqual(seriesCheck, [3, 1, 2]);
673 | });
674 |
675 | test('findIndexSeries', async (t) => {
676 | const seriesCheck = [];
677 | const foundNum = await findIndexSeries([3, 1, 2], async (num, index, array) => {
678 | await delay(num * 100);
679 | t.is(array[index], num);
680 | seriesCheck.push(num);
681 | return num === 2;
682 | });
683 | t.is(foundNum, 2);
684 | t.deepEqual(seriesCheck, [3, 1, 2]);
685 | });
686 |
687 | test('someSeries', async (t) => {
688 | const seriesCheck = [];
689 | const isIncluded = await someSeries([3, 1, 2], async (num, index, array) => {
690 | await delay(num * 100);
691 | t.is(array[index], num);
692 | seriesCheck.push(num);
693 | return num === 2;
694 | });
695 | t.true(isIncluded);
696 | t.deepEqual(seriesCheck, [3, 1, 2]);
697 | });
698 |
699 | test('everySeries', async (t) => {
700 | const seriesCheck = [];
701 | const allIncluded = await everySeries([3, 1, 2], async (num, index, array) => {
702 | await delay(num * 100);
703 | t.is(array[index], num);
704 | seriesCheck.push(num);
705 | return typeof num === 'number';
706 | });
707 | t.true(allIncluded);
708 | t.deepEqual(seriesCheck, [3, 1, 2]);
709 | });
710 |
711 | test('filterSeries', async (t) => {
712 | const seriesCheck = [];
713 | const numbers = await filterSeries([2, 1, '3'], async (num) => {
714 | await delay(num * 100);
715 | seriesCheck.push(num);
716 | return typeof num === 'number';
717 | });
718 | t.deepEqual(numbers, [2, 1]);
719 | t.deepEqual(seriesCheck, [2, 1, '3']);
720 | });
721 |
--------------------------------------------------------------------------------