From 5b0e7f6903599ad6bb4475429e2199f8542306b8 Mon Sep 17 00:00:00 2001 From: nathan-pichon Date: Tue, 31 Jan 2023 15:14:26 +0100 Subject: [PATCH 1/6] feat(utils): add reduceAsync --- docs/docs/core/utils/async.md | 1 + .../core/utils/lib/__tests__/async.test.js | 46 ++++++++++++++++++- packages/core/utils/lib/async.d.ts | 13 ++++-- packages/core/utils/lib/async.js | 12 +++++ packages/core/utils/lib/index.js | 3 +- 5 files changed, 70 insertions(+), 5 deletions(-) diff --git a/docs/docs/core/utils/async.md b/docs/docs/core/utils/async.md index 3dcb56be2f..6fef0e37e0 100644 --- a/docs/docs/core/utils/async.md +++ b/docs/docs/core/utils/async.md @@ -16,6 +16,7 @@ Available functions: - pipeAsync - mapAsync +- reduceAsync [See API reference](../../api/Utils) (TODO) diff --git a/packages/core/utils/lib/__tests__/async.test.js b/packages/core/utils/lib/__tests__/async.test.js index 74f9f53ea2..1cae0186ee 100644 --- a/packages/core/utils/lib/__tests__/async.test.js +++ b/packages/core/utils/lib/__tests__/async.test.js @@ -1,6 +1,6 @@ 'use strict'; -const { pipeAsync, mapAsync } = require('../async'); +const { pipeAsync, mapAsync, reduceAsync } = require('../async'); describe('Async utils', () => { describe('pipeAsync', () => { @@ -77,4 +77,48 @@ describe('Async utils', () => { expect(maxOperations).toEqual(2); }); }); + describe('reduceAsync', () => { + test('Should return a incremented number', async () => { + const numberPromiseArray = [Promise.resolve(1), Promise.resolve(2)]; + + const reduceFunc = reduceAsync(numberPromiseArray); + const result = await reduceFunc( + (previousValue, currentValue) => previousValue + currentValue, + 10 + ); + + expect(result).toEqual(13); + }); + test('Should work with mix of promises and values', async () => { + const numberMixArray = [1, Promise.resolve(2)]; + + const reduceFunc = reduceAsync(numberMixArray); + const result = await reduceFunc( + (previousValue, currentValue) => previousValue + currentValue, + 10 + ); + + expect(result).toEqual(13); + }); + test('Should throw an error', async () => { + const numberPromiseArray = [Promise.resolve(1), Promise.resolve(2)]; + + const reduceFunc = reduceAsync(numberPromiseArray); + + await expect(async () => { + await reduceFunc(() => { + throw new Error('test'); + }); + }).rejects.toThrow('test'); + }); + test('Should throw an error 2', async () => { + const numberPromiseArray = [Promise.reject(new Error('input')), Promise.resolve(2)]; + + const reduceFunc = reduceAsync(numberPromiseArray); + + await expect(async () => { + await reduceFunc(() => true); + }).rejects.toThrow('input'); + }); + }); }); diff --git a/packages/core/utils/lib/async.d.ts b/packages/core/utils/lib/async.d.ts index 3e735d4b06..2e52d2edc6 100644 --- a/packages/core/utils/lib/async.d.ts +++ b/packages/core/utils/lib/async.d.ts @@ -6,7 +6,14 @@ export type MapAsync = lodash.CurriedFunction3< >; export type ForEachAsync = ( - array: T[], - func: (element: T, index: number) => R | Promise, - options?: { concurrency?: number } + array: T[], + func: (element: T, index: number) => R | Promise, + options?: { concurrency?: number } ) => Promise; + +export type ReduceAsync = lodash.CurriedFunction3< + T[], + (accumulator: V | R, current: Awaited, index: number) => R | Promise, + V, + Promise +>; diff --git a/packages/core/utils/lib/async.js b/packages/core/utils/lib/async.js index 5a91e70fb5..8846762db1 100644 --- a/packages/core/utils/lib/async.js +++ b/packages/core/utils/lib/async.js @@ -20,6 +20,17 @@ function pipeAsync(...methods) { */ const mapAsync = curry(pMap); +/** + * @type { import('./async').ReduceAsync } + */ +const reduceAsync = curry(async (mixedArray, iteratee, initialValue) => { + let acc = initialValue; + for (let i = 0; i < mixedArray.length; i += 1) { + acc = await iteratee(acc, mixedArray[i], i); + } + return acc; +}); + /** * @type { import('./async').ForEachAsync } */ @@ -29,6 +40,7 @@ const forEachAsync = curry(async (array, func, options) => { module.exports = { mapAsync, + reduceAsync, forEachAsync, pipeAsync, }; diff --git a/packages/core/utils/lib/index.js b/packages/core/utils/lib/index.js index d6a3ccfad3..5b4f9c4b69 100644 --- a/packages/core/utils/lib/index.js +++ b/packages/core/utils/lib/index.js @@ -37,7 +37,7 @@ const providerFactory = require('./provider-factory'); const pagination = require('./pagination'); const sanitize = require('./sanitize'); const traverseEntity = require('./traverse-entity'); -const { pipeAsync, mapAsync, forEachAsync } = require('./async'); +const { pipeAsync, mapAsync, reduceAsync, forEachAsync } = require('./async'); const convertQueryParams = require('./convert-query-params'); const importDefault = require('./import-default'); const template = require('./template'); @@ -83,6 +83,7 @@ module.exports = { pagination, pipeAsync, mapAsync, + reduceAsync, forEachAsync, errors, validateYupSchema, From 477750650e10450a0f421c0d042132f458fb683c Mon Sep 17 00:00:00 2001 From: nathan-pichon Date: Mon, 13 Feb 2023 11:33:03 +0100 Subject: [PATCH 2/6] fix(async): arity for reduce func and awaited value --- packages/core/utils/lib/__tests__/async.test.js | 4 ++-- packages/core/utils/lib/async.d.ts | 6 ++++-- packages/core/utils/lib/async.js | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/core/utils/lib/__tests__/async.test.js b/packages/core/utils/lib/__tests__/async.test.js index 1cae0186ee..d2d63c5910 100644 --- a/packages/core/utils/lib/__tests__/async.test.js +++ b/packages/core/utils/lib/__tests__/async.test.js @@ -108,7 +108,7 @@ describe('Async utils', () => { await expect(async () => { await reduceFunc(() => { throw new Error('test'); - }); + }, null); }).rejects.toThrow('test'); }); test('Should throw an error 2', async () => { @@ -117,7 +117,7 @@ describe('Async utils', () => { const reduceFunc = reduceAsync(numberPromiseArray); await expect(async () => { - await reduceFunc(() => true); + await reduceFunc(() => true, null); }).rejects.toThrow('input'); }); }); diff --git a/packages/core/utils/lib/async.d.ts b/packages/core/utils/lib/async.d.ts index 2e52d2edc6..5f7a6b130a 100644 --- a/packages/core/utils/lib/async.d.ts +++ b/packages/core/utils/lib/async.d.ts @@ -1,4 +1,6 @@ -export type MapAsync = lodash.CurriedFunction3< +import { CurriedFunction3 } from 'lodash'; + +export type MapAsync = CurriedFunction3< T[], (element: T, index: number) => R | Promise, { concurrency?: number }, @@ -11,7 +13,7 @@ export type ForEachAsync = ( options?: { concurrency?: number } ) => Promise; -export type ReduceAsync = lodash.CurriedFunction3< +export type ReduceAsync = CurriedFunction3< T[], (accumulator: V | R, current: Awaited, index: number) => R | Promise, V, diff --git a/packages/core/utils/lib/async.js b/packages/core/utils/lib/async.js index 8846762db1..8d2e106111 100644 --- a/packages/core/utils/lib/async.js +++ b/packages/core/utils/lib/async.js @@ -26,10 +26,10 @@ const mapAsync = curry(pMap); const reduceAsync = curry(async (mixedArray, iteratee, initialValue) => { let acc = initialValue; for (let i = 0; i < mixedArray.length; i += 1) { - acc = await iteratee(acc, mixedArray[i], i); + acc = await iteratee(acc, await mixedArray[i], i); } return acc; -}); +}, 2); /** * @type { import('./async').ForEachAsync } From 4c5e98b3380ef60daa37eeb88f171735c5edbc28 Mon Sep 17 00:00:00 2001 From: nathan-pichon Date: Mon, 13 Feb 2023 11:40:51 +0100 Subject: [PATCH 3/6] chore(async-utils): indent --- packages/core/utils/lib/async.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/utils/lib/async.d.ts b/packages/core/utils/lib/async.d.ts index 5f7a6b130a..66ecb11819 100644 --- a/packages/core/utils/lib/async.d.ts +++ b/packages/core/utils/lib/async.d.ts @@ -8,9 +8,9 @@ export type MapAsync = CurriedFunction3< >; export type ForEachAsync = ( - array: T[], - func: (element: T, index: number) => R | Promise, - options?: { concurrency?: number } + array: T[], + func: (element: T, index: number) => R | Promise, + options?: { concurrency?: number } ) => Promise; export type ReduceAsync = CurriedFunction3< From 4fdff2f350a59da55b67c10ceab6bce077b4fa23 Mon Sep 17 00:00:00 2001 From: Nathan Pichon Date: Mon, 13 Feb 2023 14:02:56 +0100 Subject: [PATCH 4/6] chore(async-utils): update typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pierre Noël --- packages/core/utils/lib/__tests__/async.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/utils/lib/__tests__/async.test.js b/packages/core/utils/lib/__tests__/async.test.js index d2d63c5910..8f4bb5eedf 100644 --- a/packages/core/utils/lib/__tests__/async.test.js +++ b/packages/core/utils/lib/__tests__/async.test.js @@ -78,7 +78,7 @@ describe('Async utils', () => { }); }); describe('reduceAsync', () => { - test('Should return a incremented number', async () => { + test('Should return an incremented number', async () => { const numberPromiseArray = [Promise.resolve(1), Promise.resolve(2)]; const reduceFunc = reduceAsync(numberPromiseArray); From 83c297e2980f15a96c366607770b5bcaa22dcbd4 Mon Sep 17 00:00:00 2001 From: nathan-pichon Date: Mon, 13 Feb 2023 14:11:41 +0100 Subject: [PATCH 5/6] test(async-utils): update test titles --- packages/core/utils/lib/__tests__/async.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/utils/lib/__tests__/async.test.js b/packages/core/utils/lib/__tests__/async.test.js index 8f4bb5eedf..882f66e0db 100644 --- a/packages/core/utils/lib/__tests__/async.test.js +++ b/packages/core/utils/lib/__tests__/async.test.js @@ -100,7 +100,7 @@ describe('Async utils', () => { expect(result).toEqual(13); }); - test('Should throw an error', async () => { + test('Should throw an error with proper message when the provided callback throws an error', async () => { const numberPromiseArray = [Promise.resolve(1), Promise.resolve(2)]; const reduceFunc = reduceAsync(numberPromiseArray); @@ -111,7 +111,7 @@ describe('Async utils', () => { }, null); }).rejects.toThrow('test'); }); - test('Should throw an error 2', async () => { + test('Should throw an error with proper message when the input array contains a rejected Promise', async () => { const numberPromiseArray = [Promise.reject(new Error('input')), Promise.resolve(2)]; const reduceFunc = reduceAsync(numberPromiseArray); From 50293448059b1d282285b4fb51ec6fdd96c5b22d Mon Sep 17 00:00:00 2001 From: nathan-pichon Date: Tue, 14 Feb 2023 16:55:57 +0100 Subject: [PATCH 6/6] feat(async-utils): use curryN instead of curry --- packages/core/utils/lib/__tests__/async.test.js | 12 +++++++++++- packages/core/utils/lib/async.js | 6 +++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/core/utils/lib/__tests__/async.test.js b/packages/core/utils/lib/__tests__/async.test.js index 882f66e0db..281cfdf8a2 100644 --- a/packages/core/utils/lib/__tests__/async.test.js +++ b/packages/core/utils/lib/__tests__/async.test.js @@ -89,6 +89,16 @@ describe('Async utils', () => { expect(result).toEqual(13); }); + test('Should work without initial value', async () => { + const numberPromiseArray = [Promise.resolve(1), Promise.resolve(2)]; + + const reduceFunc = reduceAsync(numberPromiseArray); + const result = await reduceFunc( + (previousValue, currentValue) => (previousValue || 10) + currentValue + ); + + expect(result).toEqual(13); + }); test('Should work with mix of promises and values', async () => { const numberMixArray = [1, Promise.resolve(2)]; @@ -108,7 +118,7 @@ describe('Async utils', () => { await expect(async () => { await reduceFunc(() => { throw new Error('test'); - }, null); + }); }).rejects.toThrow('test'); }); test('Should throw an error with proper message when the input array contains a rejected Promise', async () => { diff --git a/packages/core/utils/lib/async.js b/packages/core/utils/lib/async.js index 8d2e106111..e5dd3dbe1b 100644 --- a/packages/core/utils/lib/async.js +++ b/packages/core/utils/lib/async.js @@ -1,7 +1,7 @@ 'use strict'; const pMap = require('p-map'); -const { curry } = require('lodash/fp'); +const { curry, curryN } = require('lodash/fp'); function pipeAsync(...methods) { return async (data) => { @@ -23,13 +23,13 @@ const mapAsync = curry(pMap); /** * @type { import('./async').ReduceAsync } */ -const reduceAsync = curry(async (mixedArray, iteratee, initialValue) => { +const reduceAsync = curryN(2, async (mixedArray, iteratee, initialValue) => { let acc = initialValue; for (let i = 0; i < mixedArray.length; i += 1) { acc = await iteratee(acc, await mixedArray[i], i); } return acc; -}, 2); +}); /** * @type { import('./async').ForEachAsync }