From 0168a2758a4932bf2c29a8256b7bb877e0ff4868 Mon Sep 17 00:00:00 2001 From: nathan-pichon Date: Thu, 8 Dec 2022 17:17:11 +0100 Subject: [PATCH] feat(core-utils): add mapAsync and reduceAsync utils --- .../core/utils/lib/__tests__/async.test.js | 70 +++++++++++++++++++ packages/core/utils/lib/async.js | 69 ++++++++++++++++++ packages/core/utils/lib/index.js | 2 +- packages/core/utils/lib/pipe-async.js | 13 ---- packages/core/utils/lib/sanitize/index.js | 2 +- .../core/utils/lib/sanitize/sanitizers.js | 2 +- 6 files changed, 142 insertions(+), 16 deletions(-) create mode 100644 packages/core/utils/lib/__tests__/async.test.js create mode 100644 packages/core/utils/lib/async.js delete mode 100644 packages/core/utils/lib/pipe-async.js diff --git a/packages/core/utils/lib/__tests__/async.test.js b/packages/core/utils/lib/__tests__/async.test.js new file mode 100644 index 0000000000..6f0e0e3bfb --- /dev/null +++ b/packages/core/utils/lib/__tests__/async.test.js @@ -0,0 +1,70 @@ +'use strict'; + +const { mapAsync, reduceAsync } = require('../async'); + +describe('Async utils', () => { + describe('mapAsync', () => { + test('Should return a simple array of numbers', async () => { + const numberPromiseArray = [Promise.resolve(1), Promise.resolve(2)]; + + const mapFunc = mapAsync(numberPromiseArray); + const result = await mapFunc((number) => number + 1); + + expect(result).toEqual([2, 3]); + }); + test('Should work with mix of promises and values', async () => { + const numberMixArray = [1, Promise.resolve(2)]; + + const mapFunc = mapAsync(numberMixArray); + const result = await mapFunc((number) => number + 1); + + expect(result).toEqual([2, 3]); + }); + test('Should throw an error', async () => { + const numberPromiseArray = [Promise.resolve(1), Promise.resolve(2)]; + + const mapFunc = mapAsync(numberPromiseArray); + + await expect(async () => { + await mapFunc(() => { + throw new Error('test'); + }); + }).rejects.toThrow('test'); + }); + }); + 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'); + }); + }); +}); diff --git a/packages/core/utils/lib/async.js b/packages/core/utils/lib/async.js new file mode 100644 index 0000000000..183fc51567 --- /dev/null +++ b/packages/core/utils/lib/async.js @@ -0,0 +1,69 @@ +'use strict'; + +function pipeAsync(...methods) { + return async (data) => { + let res = data; + + for (const method of methods) { + res = await method(res); + } + + return res; + }; +} + +/** + * Map function callback. + * @callback mapAsyncCallback + * @param {*} value + * @param {number} index + */ +/** + * Async iteration over an array of promises + * @param {promise<*>[]} promiseArray + * @returns {function(callback: mapAsyncCallback): promise<*[]>} + */ +function mapAsync(promiseArray) { + /** + * @param {mapAsyncCallback} callback + * @returns promise<*[]> + */ + return (callback) => { + const transformedPromiseArray = promiseArray.map(async (promiseValue, index) => { + const value = await promiseValue; + return callback(value, index); + }); + return Promise.all(transformedPromiseArray); + }; +} + +/** + * Reduce function callback. + * @callback reduceAsyncCallback + * @param {*} previousValue + * @param {*} currentValue + * @param {number} index + */ +/** + * Async chain over an array of promises + * @param {promise<*>[]} promiseArray + * @returns {function(callback: reduceAsyncCallback, initialValue?: *): promise<*>} + */ +function reduceAsync(promiseArray) { + /** + * @param {reduceAsyncCallback} callback + * @param {*} [initialValue] + * @returns promise<*> + */ + return (callback, initialValue) => + promiseArray.reduce(async (previousPromise, currentValue, index) => { + const previousValue = await previousPromise; + return callback(previousValue, await currentValue, index); + }, Promise.resolve(initialValue)); +} + +module.exports = { + mapAsync, + reduceAsync, + pipeAsync, +}; diff --git a/packages/core/utils/lib/index.js b/packages/core/utils/lib/index.js index 3d383e9c60..fba8205605 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 = require('./pipe-async'); +const { pipeAsync } = require('./async'); const convertQueryParams = require('./convert-query-params'); const importDefault = require('./import-default'); const template = require('./template'); diff --git a/packages/core/utils/lib/pipe-async.js b/packages/core/utils/lib/pipe-async.js deleted file mode 100644 index 213a96a979..0000000000 --- a/packages/core/utils/lib/pipe-async.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -module.exports = - (...methods) => - async (data) => { - let res = data; - - for (const method of methods) { - res = await method(res); - } - - return res; - }; diff --git a/packages/core/utils/lib/sanitize/index.js b/packages/core/utils/lib/sanitize/index.js index 11735c1efe..4da7ace226 100644 --- a/packages/core/utils/lib/sanitize/index.js +++ b/packages/core/utils/lib/sanitize/index.js @@ -4,7 +4,7 @@ const { isArray } = require('lodash/fp'); const traverseEntity = require('../traverse-entity'); const { getNonWritableAttributes } = require('../content-types'); -const pipeAsync = require('../pipe-async'); +const { pipeAsync } = require('../async'); const visitors = require('./visitors'); const sanitizers = require('./sanitizers'); diff --git a/packages/core/utils/lib/sanitize/sanitizers.js b/packages/core/utils/lib/sanitize/sanitizers.js index 87c42812c3..584c4240ab 100644 --- a/packages/core/utils/lib/sanitize/sanitizers.js +++ b/packages/core/utils/lib/sanitize/sanitizers.js @@ -2,7 +2,7 @@ const { curry } = require('lodash/fp'); -const pipeAsync = require('../pipe-async'); +const { pipeAsync } = require('../async'); const traverseEntity = require('../traverse-entity'); const { removePassword, removePrivate } = require('./visitors');