diff --git a/README.md b/README.md index 3955048..6137d70 100644 --- a/README.md +++ b/README.md @@ -15,32 +15,16 @@ A collection of array-related async utilities. ### General utils -#### `asyncMap()` +#### `asyncEvery()` -Creates a new array populated with the results of calling a provided asynchronous function on every element in the calling array. - -Note: For optimization purposes, all iterations are ran concurrently. If you rely on any side effects, consider `asyncMapStrict()` instead. +Tests whether all elements in the array pass the test implemented by the provided asynchronous function. It returns a Boolean value. ##### Sample usage ```js -import { asyncMap } from '@wojtekmaj/async-array-utils'; +import { asyncEvery } from '@wojtekmaj/async-array-utils'; -const asyncMappedArr = await asyncMap([1, 2, 3], async (el, index) => el * 2); // [2, 4, 6] -``` - -#### `asyncMapStrict()` - -Like `asyncMap()`, but runs iterations non-concurrently. - -##### Sample usage - -```js -import { asyncMapStrict } from '@wojtekmaj/async-array-utils'; - -const indexes = []; -await asyncMapStrict([1, 2, 3], async (el, index) => { indexes.push(index); return el * 2; }); // [2, 4, 6] -console.log(indexes); // [0, 1, 2] +const largerThanZero = await asyncEvery([1, 2, 3], async (el, index) => el > 0); // true ``` #### `asyncForEach()` @@ -77,6 +61,34 @@ await asyncForEachStrict( console.log(indexes); // [0, 1, 2] ``` +#### `asyncMap()` + +Creates a new array populated with the results of calling a provided asynchronous function on every element in the calling array. + +Note: For optimization purposes, all iterations are ran concurrently. If you rely on any side effects, consider `asyncMapStrict()` instead. + +##### Sample usage + +```js +import { asyncMap } from '@wojtekmaj/async-array-utils'; + +const asyncMappedArr = await asyncMap([1, 2, 3], async (el, index) => el * 2); // [2, 4, 6] +``` + +#### `asyncMapStrict()` + +Like `asyncMap()`, but runs iterations non-concurrently. + +##### Sample usage + +```js +import { asyncMapStrict } from '@wojtekmaj/async-array-utils'; + +const indexes = []; +await asyncMapStrict([1, 2, 3], async (el, index) => { indexes.push(index); return el * 2; }); // [2, 4, 6] +console.log(indexes); // [0, 1, 2] +``` + #### `asyncReduce()` Executes a reducer asynchronous function (that you provide) on each element of the array, resulting in a single output value. @@ -93,6 +105,32 @@ const result = await asyncReduce( ); // 6 ``` +#### `asyncSome()` + +Tests whether at least one element in the array pass the test implemented by the provided asynchronous function. It returns a Boolean value. + +Note: For optimization purposes, all iterations are ran concurrently. If you rely on any side effects or execution order, consider `asyncMapStrict()` instead. + +##### Sample usage + +```js +import { asyncSome } from '@wojtekmaj/async-array-utils'; + +const largerThanZero = await asyncSome([1, 2, 3], async (el, index) => el > 0); // true +``` + +#### `asyncSomeStrict()` + +Like `asyncSome()`, but runs iterations non-concurrently. + +##### Sample usage + +```js +import { asyncSomeStrict } from '@wojtekmaj/async-array-utils'; + +const largerThanZero = await asyncSomeStrict([1, 2, 3], async (el, index) => el > 0); // true +``` + ## License The MIT License. diff --git a/src/every.js b/src/every.js new file mode 100644 index 0000000..8468194 --- /dev/null +++ b/src/every.js @@ -0,0 +1,20 @@ +import asyncForEach from './forEach'; + +export default async function asyncSome(arr, fn) { + return new Promise((resolve) => { + let resolved = false; + // eslint-disable-next-line no-shadow + asyncForEach(arr, async (cur, idx, arr) => { + const result = await fn(cur, idx, arr); + + if (!result) { + resolved = true; + resolve(false); + } + }).then(() => { + if (!resolved) { + resolve(true); + } + }); + }); +} diff --git a/src/every.spec.js b/src/every.spec.js new file mode 100644 index 0000000..70cafec --- /dev/null +++ b/src/every.spec.js @@ -0,0 +1,89 @@ +import asyncEvery from './every'; + +import { + inputArr, +} from '../test-utils'; + +function largerOrEqualThanZero(x) { + return x >= 0; +} + +function largerOrEqualThanZeroInRandomTime(x) { + return new Promise( + (resolve) => setTimeout(() => { + resolve(x >= 0); + }, Math.random() * 100), + ); +} + +function largerThanOneHundred(x) { + return x > 100; +} + +function largerThanOneHundredInRandomTime(x) { + return new Promise( + (resolve) => setTimeout(() => { + resolve(x > 100); + }, Math.random() * 100), + ); +} + +describe('asyncEvery()', () => { + it.skip('assertions below are valid for synchronous .every()', () => { + const mapper = jest.fn().mockImplementation(largerOrEqualThanZero); + + inputArr.every(mapper); + + expect.assertions(1 + inputArr.length); + + expect(mapper).toHaveBeenCalledTimes(inputArr.length); + inputArr.forEach((el, idx) => { + expect(mapper).toHaveBeenCalledWith(el, idx, inputArr); + }); + }); + + it('iterates over values properly', async () => { + const mapper = jest.fn().mockImplementation(largerOrEqualThanZeroInRandomTime); + + await asyncEvery(inputArr, mapper); + + expect.assertions(1 + inputArr.length); + + expect(mapper).toHaveBeenCalledTimes(inputArr.length); + inputArr.forEach((el, idx) => { + expect(mapper).toHaveBeenCalledWith(el, idx, inputArr); + }); + }); + + it.skip('assertions below are valid for synchronous .every()', () => { + const mapper = jest.fn().mockImplementation(largerOrEqualThanZero); + + const result = inputArr.every(mapper); + + expect(result).toEqual(true); + }); + + it('returns truthy result properly', async () => { + const mapper = jest.fn().mockImplementation(largerOrEqualThanZeroInRandomTime); + + const result = await asyncEvery(inputArr, mapper); + + expect(result).toEqual(true); + }); + + it.skip('assertions below are valid for synchronous .every()', () => { + const mapper = jest.fn().mockImplementation(largerThanOneHundred); + + const result = inputArr.every(mapper); + + expect(result).toEqual(false); + }); + + it('returns falsy result properly', async () => { + const mapper = jest.fn().mockImplementation(largerThanOneHundredInRandomTime); + + const result = await asyncEvery(inputArr, mapper); + + expect(result).toEqual(false); + }); +}); diff --git a/src/index.js b/src/index.js index 7126b3c..9b98c2c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,13 +1,19 @@ import asyncForEach from './forEach'; import asyncForEachStrict from './forEach_strict'; +import asyncEvery from './every'; import asyncMap from './map'; import asyncMapStrict from './map_strict'; import asyncReduce from './reduce'; +import asyncSome from './some'; +import asyncSomeStrict from './some_strict'; export { asyncForEach, asyncForEachStrict, + asyncEvery, asyncMap, asyncMapStrict, asyncReduce, + asyncSome, + asyncSomeStrict, }; diff --git a/src/index.spec.js b/src/index.spec.js index 579bccc..8971d2c 100644 --- a/src/index.spec.js +++ b/src/index.spec.js @@ -1,9 +1,12 @@ import { asyncForEach, asyncForEachStrict, + asyncEvery, asyncMap, asyncMapStrict, asyncReduce, + asyncSome, + asyncSomeStrict, } from './index'; describe('index', () => { @@ -15,6 +18,10 @@ describe('index', () => { expect(asyncForEachStrict).toBeInstanceOf(Function); }); + it('has asyncEvery exported properly', () => { + expect(asyncEvery).toBeInstanceOf(Function); + }); + it('has asyncMap exported properly', () => { expect(asyncMap).toBeInstanceOf(Function); }); @@ -26,4 +33,12 @@ describe('index', () => { it('has asyncReduce exported properly', () => { expect(asyncReduce).toBeInstanceOf(Function); }); + + it('has asyncSome exported properly', () => { + expect(asyncSome).toBeInstanceOf(Function); + }); + + it('has asyncSomeStrict exported properly', () => { + expect(asyncSomeStrict).toBeInstanceOf(Function); + }); }); diff --git a/src/some.js b/src/some.js new file mode 100644 index 0000000..bc7c850 --- /dev/null +++ b/src/some.js @@ -0,0 +1,20 @@ +import asyncForEach from './forEach'; + +export default async function asyncSome(arr, fn) { + return new Promise((resolve) => { + let resolved = false; + // eslint-disable-next-line no-shadow + asyncForEach(arr, async (cur, idx, arr) => { + const result = await fn(cur, idx, arr); + + if (result) { + resolved = true; + resolve(true); + } + }).then(() => { + if (!resolved) { + resolve(false); + } + }); + }); +} diff --git a/src/some.spec.js b/src/some.spec.js new file mode 100644 index 0000000..2a6080c --- /dev/null +++ b/src/some.spec.js @@ -0,0 +1,69 @@ +import asyncSome from './some'; + +import { + inputArr, + largerThanTwo, + largerThanTwoInRandomTime, + largerThanOneHundred, + largerThanOneHundredInRandomTime, +} from '../test-utils'; + +const firstElementLargerThanTwo = inputArr.findIndex(largerThanTwo); + +describe('asyncSome()', () => { + it.skip('assertions below are valid for synchronous .some()', () => { + const mapper = jest.fn().mockImplementation(largerThanTwo); + + inputArr.some(mapper); + + expect.assertions(firstElementLargerThanTwo); + + inputArr.slice(0, firstElementLargerThanTwo).forEach((el, idx) => { + expect(mapper).toHaveBeenCalledWith(el, idx, inputArr); + }); + }); + + it('iterates over values properly', async () => { + const mapper = jest.fn().mockImplementation(largerThanTwoInRandomTime); + + await asyncSome(inputArr, mapper); + + expect.assertions(firstElementLargerThanTwo); + + inputArr.slice(0, firstElementLargerThanTwo).forEach((el, idx) => { + expect(mapper).toHaveBeenCalledWith(el, idx, inputArr); + }); + }); + + it.skip('assertions below are valid for synchronous .some()', () => { + const mapper = jest.fn().mockImplementation(largerThanTwo); + + const result = inputArr.some(mapper); + + expect(result).toEqual(true); + }); + + it('returns truthy result properly', async () => { + const mapper = jest.fn().mockImplementation(largerThanTwoInRandomTime); + + const result = await asyncSome(inputArr, mapper); + + expect(result).toEqual(true); + }); + + it.skip('assertions below are valid for synchronous .some()', () => { + const mapper = jest.fn().mockImplementation(largerThanOneHundred); + + const result = inputArr.some(mapper); + + expect(result).toEqual(false); + }); + + it('returns falsy result properly', async () => { + const mapper = jest.fn().mockImplementation(largerThanOneHundredInRandomTime); + + const result = await asyncSome(inputArr, mapper); + + expect(result).toEqual(false); + }); +}); diff --git a/src/some_strict.js b/src/some_strict.js new file mode 100644 index 0000000..a44001f --- /dev/null +++ b/src/some_strict.js @@ -0,0 +1,14 @@ +export default async function asyncSomeStrict(arr, fn) { + for (let idx = 0; idx < arr.length; idx += 1) { + const cur = arr[idx]; + + // eslint-disable-next-line no-await-in-loop + const result = await fn(cur, idx, arr); + + if (result) { + return true; + } + } + + return false; +} diff --git a/src/some_strict.spec.js b/src/some_strict.spec.js new file mode 100644 index 0000000..6b5f664 --- /dev/null +++ b/src/some_strict.spec.js @@ -0,0 +1,71 @@ +import asyncSomeStrict from './some_strict'; + +import { + inputArr, + largerThanTwo, + largerThanTwoInRandomTime, + largerThanOneHundred, + largerThanOneHundredInRandomTime, +} from '../test-utils'; + +const firstElementLargerThanTwo = inputArr.findIndex(largerThanTwo); + +describe('asyncSomeStrict()', () => { + it('assertions below are valid for synchronous .some()', () => { + const mapper = jest.fn().mockImplementation(largerThanTwo); + + inputArr.some(mapper); + + expect.assertions(1 + firstElementLargerThanTwo); + + expect(mapper).toHaveBeenCalledTimes(firstElementLargerThanTwo + 1); + inputArr.slice(0, firstElementLargerThanTwo).forEach((el, idx) => { + expect(mapper).toHaveBeenCalledWith(el, idx, inputArr); + }); + }); + + it('iterates over values properly', async () => { + const mapper = jest.fn().mockImplementation(largerThanTwoInRandomTime); + + await asyncSomeStrict(inputArr, mapper); + + expect.assertions(1 + firstElementLargerThanTwo); + + expect(mapper).toHaveBeenCalledTimes(firstElementLargerThanTwo + 1); + inputArr.slice(0, firstElementLargerThanTwo).forEach((el, idx) => { + expect(mapper).toHaveBeenCalledWith(el, idx, inputArr); + }); + }); + + it('assertions below are valid for synchronous .some()', () => { + const mapper = jest.fn().mockImplementation(largerThanTwo); + + const result = inputArr.some(mapper); + + expect(result).toEqual(true); + }); + + it('returns truthy result properly', async () => { + const mapper = jest.fn().mockImplementation(largerThanTwoInRandomTime); + + const result = await asyncSomeStrict(inputArr, mapper); + + expect(result).toEqual(true); + }); + + it('assertions below are valid for synchronous .some()', () => { + const mapper = jest.fn().mockImplementation(largerThanOneHundred); + + const result = inputArr.some(mapper); + + expect(result).toEqual(false); + }); + + it('returns falsy result properly', async () => { + const mapper = jest.fn().mockImplementation(largerThanOneHundredInRandomTime); + + const result = await asyncSomeStrict(inputArr, mapper); + + expect(result).toEqual(false); + }); +}); diff --git a/test-utils.js b/test-utils.js index ea530ca..e9852be 100644 --- a/test-utils.js +++ b/test-utils.js @@ -10,6 +10,30 @@ export function duplicateInRandomTime(x) { ); } +export function largerThanTwo(x) { + return x > 2; +} + +export function largerThanTwoInRandomTime(x) { + return new Promise( + (resolve) => setTimeout(() => { + resolve(x > 2); + }, Math.random() * 100), + ); +} + +export function largerThanOneHundred(x) { + return x > 100; +} + +export function largerThanOneHundredInRandomTime(x) { + return new Promise( + (resolve) => setTimeout(() => { + resolve(x > 100); + }, Math.random() * 100), + ); +} + export function makePushDuplicate() { const arr = []; @@ -30,5 +54,5 @@ export function makePushDuplicateInRandomTime() { return [arr, pushDuplicate]; } -export const inputArr = [1, 2, 3, 4, 5]; -export const doubleInputArr = [2, 4, 6, 8, 10]; +export const inputArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +export const doubleInputArr = inputArr.map(duplicate);