1'use strict'; 2const AggregateError = require('aggregate-error'); 3 4module.exports = async ( 5 iterable, 6 mapper, 7 { 8 concurrency = Infinity, 9 stopOnError = true 10 } = {} 11) => { 12 return new Promise((resolve, reject) => { 13 if (typeof mapper !== 'function') { 14 throw new TypeError('Mapper function is required'); 15 } 16 17 if (!((Number.isSafeInteger(concurrency) || concurrency === Infinity) && concurrency >= 1)) { 18 throw new TypeError(`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`); 19 } 20 21 const result = []; 22 const errors = []; 23 const iterator = iterable[Symbol.iterator](); 24 let isRejected = false; 25 let isIterableDone = false; 26 let resolvingCount = 0; 27 let currentIndex = 0; 28 29 const next = () => { 30 if (isRejected) { 31 return; 32 } 33 34 const nextItem = iterator.next(); 35 const index = currentIndex; 36 currentIndex++; 37 38 if (nextItem.done) { 39 isIterableDone = true; 40 41 if (resolvingCount === 0) { 42 if (!stopOnError && errors.length !== 0) { 43 reject(new AggregateError(errors)); 44 } else { 45 resolve(result); 46 } 47 } 48 49 return; 50 } 51 52 resolvingCount++; 53 54 (async () => { 55 try { 56 const element = await nextItem.value; 57 result[index] = await mapper(element, index); 58 resolvingCount--; 59 next(); 60 } catch (error) { 61 if (stopOnError) { 62 isRejected = true; 63 reject(error); 64 } else { 65 errors.push(error); 66 resolvingCount--; 67 next(); 68 } 69 } 70 })(); 71 }; 72 73 for (let i = 0; i < concurrency; i++) { 74 next(); 75 76 if (isIterableDone) { 77 break; 78 } 79 } 80 }); 81}; 82