From e8c7db0fff8067d3a917f056ec1ff4b50df7e8d6 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 15 Dec 2024 10:37:56 -0800 Subject: [PATCH] allow passing values/resolvers or all values for all --- _internal/areAllValuesNonfunctions.js | 20 ++++- _internal/areAnyValuesPromises.js | 20 ++++- _internal/functionArrayAll.js | 3 +- all.js | 123 +++++++++++++++++++++----- assign.js | 2 +- test.js | 66 ++++++++++---- 6 files changed, 183 insertions(+), 51 deletions(-) diff --git a/_internal/areAllValuesNonfunctions.js b/_internal/areAllValuesNonfunctions.js index ce67be41..873c9d21 100644 --- a/_internal/areAllValuesNonfunctions.js +++ b/_internal/areAllValuesNonfunctions.js @@ -1,16 +1,28 @@ +const isArray = require('./isArray') + /** * @name areAllValuesNonfunctions * * @synopsis * ```coffeescript [specscript] * areAllValuesNonfunctions(values Array) -> boolean + * areAllValuesNonfunctions(values Object) -> boolean * ``` */ const areAllValuesNonfunctions = function (values) { - const length = values.length - let index = -1 - while (++index < length) { - if (typeof values[index] == 'function') { + if (isArray(values)) { + const length = values.length + let index = -1 + while (++index < length) { + if (typeof values[index] == 'function') { + return false + } + } + return true + } + + for (const key in values) { + if (typeof values[key] == 'function') { return false } } diff --git a/_internal/areAnyValuesPromises.js b/_internal/areAnyValuesPromises.js index 9841e53e..e4a61b57 100644 --- a/_internal/areAnyValuesPromises.js +++ b/_internal/areAnyValuesPromises.js @@ -1,3 +1,4 @@ +const isArray = require('./isArray') const isPromise = require('./isPromise') /** @@ -6,13 +7,24 @@ const isPromise = require('./isPromise') * @synopsis * ```coffeescript [specscript] * areAnyValuesPromises(values Array) -> boolean + * areAnyValuesPromises(values Object) -> boolean * ``` */ const areAnyValuesPromises = function (values) { - const length = values.length - let index = -1 - while (++index < length) { - const value = values[index] + if (isArray(values)) { + const length = values.length + let index = -1 + while (++index < length) { + const value = values[index] + if (isPromise(value)) { + return true + } + } + return false + } + + for (const key in values) { + const value = values[key] if (isPromise(value)) { return true } diff --git a/_internal/functionArrayAll.js b/_internal/functionArrayAll.js index 5c2a7401..77559e21 100644 --- a/_internal/functionArrayAll.js +++ b/_internal/functionArrayAll.js @@ -14,7 +14,8 @@ const functionArrayAll = function (funcs, args) { result = Array(funcsLength) let funcsIndex = -1, isAsync = false while (++funcsIndex < funcsLength) { - const resultItem = funcs[funcsIndex](...args) + const f = funcs[funcsIndex] + const resultItem = typeof f == 'function' ? f(...args) : f if (isPromise(resultItem)) { isAsync = true } diff --git a/all.js b/all.js index 891cd319..2fd88385 100644 --- a/all.js +++ b/all.js @@ -1,5 +1,8 @@ +const isPromise = require('./_internal/isPromise') const areAnyValuesPromises = require('./_internal/areAnyValuesPromises') +const areAllValuesNonfunctions = require('./_internal/areAllValuesNonfunctions') const promiseAll = require('./_internal/promiseAll') +const promiseObjectAll = require('./_internal/promiseObjectAll') const isArray = require('./_internal/isArray') const __ = require('./_internal/placeholder') const curry2 = require('./_internal/curry2') @@ -9,21 +12,54 @@ const functionArrayAllSeries = require('./_internal/functionArrayAllSeries') const functionObjectAll = require('./_internal/functionObjectAll') /** - * @name all + * @name _allValues * * @synopsis * ```coffeescript [specscript] - * all(...args, funcsArray Array) -> result Promise|Array - * - * all(funcsArray Array)(...args) -> result Promise|Array - * - * all(...args, funcsObject Object) -> result Promise|Object + * _allValues(values Array) -> Promise + * _allValues(values Object) -> Promise + * ``` + */ +const _allValues = function (values) { + if (isArray(values)) { + return areAnyValuesPromises(values) + ? promiseAll(values) + : values + } + return areAnyValuesPromises(values) + ? promiseObjectAll(values) + : values +} + +/** + * @name all * - * all(funcsObject Object)(...args) -> result Promise|Object + * @synopsis + * ```coffeescript [specscript] + * all(values Promise|Array) -> result Promise|Array + * all(values Promise|Object) -> result Promise|Object + * + * all( + * ...args, + * resolversOrValues Array + * ) -> result Promise|Array + * + * all( + * resolversOrValues Array + * )(...args) -> result Promise|Array + * + * all( + * ...args, + * resolversOrValues Object + * ) -> result Promise|Object + * + * all( + * resolversOrValues Object + * )(...args) -> result Promise|Object * ``` * * @description - * Function executor and composer. Accepts either an array of functions or an object of functions. Calls each function of the provided array or object in parallel with the provided arguments. Returns either an array or object of the execution results. + * Calls an array or object of resolver functions or values `resolversOrValues` with provided arguments. * * ```javascript [playground] * const createArrayOfGreetingsFor = all([ @@ -32,24 +68,29 @@ const functionObjectAll = require('./_internal/functionObjectAll') * name => `Hello ${name}`, * ]) * - * const arrayOfGreetingsForFred = createArrayOfGreetingsFor('Fred') - * - * console.log(arrayOfGreetingsForFred) - * // ['Hi Fred', 'Hey Fred', 'Hello Fred'] + * const arrayOfGreetingsFor1 = createArrayOfGreetingsFor('1') * - * const createObjectOfGreetingsFor = all({ - * hi: name => `Hi ${name}`, - * hey: name => `Hey ${name}`, - * hello: name => `Hello ${name}`, - * }) + * console.log(arrayOfGreetingsFor1) + * // ['Hi 1', 'Hey 1', 'Hello 1'] + * ``` * - * const objectOfGreetingsForJane = createObjectOfGreetingsFor('Jane') + * If provided only values for `resolversOrValues`, returns an array or object with the same shape as `resolversOrValues` with any Promises resolved. * - * console.log(objectOfGreetingsForJane) - * // { hi: 'Hi Jane', hey: 'Hey Jane', hello: 'Hello Jane' } + * ```javascript [playground] + * all([ + * Promise.resolve(1), + * Promise.resolve(2), + * 3, + * ]).then(console.log) // [1, 2, 3] + * + * all({ + * a: Promise.resolve(1), + * b: Promise.resolve(2), + * c: 3, + * }).then(console.log) // { a: 1, b: 2, c: 3 } * ``` * - * `all` can simultaneously compose objects and handle promises. + * `all` can be used in a pipeline to compose and manpulate data. * * ```javascript [playground] * const identity = value => value @@ -72,6 +113,14 @@ const functionObjectAll = require('./_internal/functionObjectAll') * getAndLogUserById('1') // Got user {"_id":1,"name":"George"} by id 1 * ``` * + * Values passed in resolver position are set on the result object or array directly. If any of these values are promises, they are resolved for their values before being set on the result object or array. + * + * ```javascript [playground] + * all({}, [ + * Promise.resolve(1), + * ]) + * ``` + * * Any promises passed in argument position are resolved for their values before further execution. This only applies to the eager version of the API. * * ```javascript [playground] @@ -84,8 +133,37 @@ const functionObjectAll = require('./_internal/functionObjectAll') * * @execution concurrent */ - const all = function (...args) { + if (args.length == 1) { + const resolversOrValues = args[0] + if (isPromise(resolversOrValues)) { + return resolversOrValues.then(_allValues) + } + if (areAllValuesNonfunctions(resolversOrValues)) { + return _allValues(resolversOrValues) + } + return isArray(resolversOrValues) + ? curryArgs2(functionArrayAll, resolversOrValues, __) + : curryArgs2(functionObjectAll, resolversOrValues, __) + } + + const resolversOrValues = args[args.length - 1] + const argValues = args.slice(0, -1) + + if (areAnyValuesPromises(argValues)) { + return isArray(resolversOrValues) + ? promiseAll(argValues) + .then(curry2(functionArrayAll, resolversOrValues, __)) + : promiseAll(argValues) + .then(curry2(functionObjectAll, resolversOrValues, __)) + } + + return isArray(resolversOrValues) + ? functionArrayAll(resolversOrValues, argValues) + : functionObjectAll(resolversOrValues, argValues) + + /* + //////////////////////////////////////////////////////////////// const funcs = args.pop() if (args.length == 0) { return isArray(funcs) @@ -102,6 +180,7 @@ const all = function (...args) { return isArray(funcs) ? functionArrayAll(funcs, args) : functionObjectAll(funcs, args) + */ } /** diff --git a/assign.js b/assign.js index 57d6ca21..80cddfc1 100644 --- a/assign.js +++ b/assign.js @@ -60,7 +60,7 @@ const _assign = function (object, funcs) { * // { numbers: [1, 2, 3, 4, 5], total: 15 } * ``` * - * Values passed in resolver position are set on the result object directly. If any of these values are promises, they are resolved for their values before being assigned to the result object. + * Values passed in resolver position are set on the result object directly. If any of these values are promises, they are resolved for their values before being set on the result object. * * ```javascript [playground] * assign({}, { diff --git a/test.js b/test.js index 77a8ef11..729b2941 100644 --- a/test.js +++ b/test.js @@ -435,6 +435,21 @@ describe('rubico', () => { ], ) + ade( + all([ + Array.of, + Array.of, + Array.of, + 4, + ])(1, 2, 3), + [ + [1, 2, 3], + [1, 2, 3], + [1, 2, 3], + 4, + ], + ) + ade( await all(Promise.resolve(1), 2, Promise.resolve(3), [ Array.of, @@ -486,6 +501,21 @@ describe('rubico', () => { c: [1, 2, 3], }, ) + + ade( + all({ + a: Array.of, + b: Array.of, + c: Array.of, + d: 4, + })(1, 2, 3), + { + a: [1, 2, 3], + b: [1, 2, 3], + c: [1, 2, 3], + d: 4, + }, + ) }) it('maps input to array of sync functions', async () => { ade(all([hi, hi, hi])('hi'), ['hihi', 'hihi', 'hihi']) @@ -517,25 +547,23 @@ describe('rubico', () => { ['1hey', '1hey', '1hi'], ) }) - it('all([])() -> []', async () => { - ade(all([])(), []) - ade(all([])('hey'), []) - }) - it('all({})() -> {}', async () => { - ade(all({})(), {}) - ade(all({})('hey'), {}) - }) - it('TypeError for all([\'hey\'])()', async () => { - assert.throws( - () => all(['hey'])(), - new TypeError('funcs[funcsIndex] is not a function'), - ) - }) - it('{} for Set<[func]>; no functions exposed via in', async () => { - assert.deepEqual(all(new Set([() => 'yo']))('hey'), {}) - }) - it('{} for Map<[[1, func]]>', async () => { - assert.deepEqual(all(new Map([[1, () => 'yo']]))('hey'), {}) + it('only nonfunctions', async () => { + ade(all([1, 2, 3]), [1, 2, 3]) + ade(await all(Promise.resolve([Promise.resolve(1), 2, 3])), [1, 2, 3]) + ade(await all([Promise.resolve(1), Promise.resolve(2), 3]), [1, 2, 3]) + ade(all([]), []) + ade(all({ a: 1, b: 2, c: 3 }), { a: 1, b: 2, c: 3 }) + ade(await all(Promise.resolve({ + a: Promise.resolve(1), + b: 2, + c: 3, + })), { a: 1, b: 2, c: 3 }) + ade(await all({ + a: Promise.resolve(1), + b: Promise.resolve(2), + c: 3, + }), { a: 1, b: 2, c: 3 }) + ade(all({}), {}) }) })