├── .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 [![Build Status](https://travis-ci.org/toniov/p-iteration.svg?branch=master)](https://travis-ci.org/toniov/p-iteration) [![NPM version](https://badge.fury.io/js/p-iteration.svg)](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 | --------------------------------------------------------------------------------