Skip to content

Commit d5fc623

Browse files
authored
feat: add all function to the async module (#305)
1 parent 77cb220 commit d5fc623

File tree

7 files changed

+176
-3
lines changed

7 files changed

+176
-3
lines changed

Diff for: cdn/radash.esm.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,28 @@ const parallel = async (limit, array, func) => {
408408
}
409409
return results.map((r) => r.result);
410410
};
411+
async function all(promises) {
412+
const entries = isArray(promises) ? promises.map((p) => [null, p]) : Object.entries(promises);
413+
const results = await Promise.all(
414+
entries.map(
415+
([key, value]) => value.then((result) => ({ result, exc: null, key })).catch((exc) => ({ result: null, exc, key }))
416+
)
417+
);
418+
const exceptions = results.filter((r) => r.exc);
419+
if (exceptions.length > 0) {
420+
throw new AggregateError(exceptions.map((e) => e.exc));
421+
}
422+
if (isArray(promises)) {
423+
return results.map((r) => r.result);
424+
}
425+
return results.reduce(
426+
(acc, item) => ({
427+
...acc,
428+
[item.key]: item.result
429+
}),
430+
{}
431+
);
432+
}
411433
const retry = async (options, func) => {
412434
const times = options?.times ?? 3;
413435
const delay = options?.delay;
@@ -888,4 +910,4 @@ const trim = (str, charsToTrim = " ") => {
888910
return str.replace(regex, "");
889911
};
890912

891-
export { alphabetical, assign, boil, callable, camel, capitalize, chain, clone, cluster, compose, construct, counting, crush, dash, debounce, defer, diff, draw, first, flat, fork, get, group, guard, intersects, invert, isArray, isDate, isEmpty, isEqual, isFloat, isFunction, isInt, isNumber, isObject, isPrimitive, isString, isSymbol, iterate, keys, last, list, listify, lowerize, map, mapEntries, mapKeys, mapValues, max, memo, merge, min, objectify, omit, parallel, partial, partob, pascal, pick, proxied, random, range, reduce, replace, replaceOrAppend, retry, select, series, set, shake, shift, shuffle, sift, sleep, snake, sort, sum, template, throttle, title, toFloat, toInt, toggle, trim, tryit as try, tryit, uid, unique, upperize, zip, zipToObject };
913+
export { all, alphabetical, assign, boil, callable, camel, capitalize, chain, clone, cluster, compose, construct, counting, crush, dash, debounce, defer, diff, draw, first, flat, fork, get, group, guard, intersects, invert, isArray, isDate, isEmpty, isEqual, isFloat, isFunction, isInt, isNumber, isObject, isPrimitive, isString, isSymbol, iterate, keys, last, list, listify, lowerize, map, mapEntries, mapKeys, mapValues, max, memo, merge, min, objectify, omit, parallel, partial, partob, pascal, pick, proxied, random, range, reduce, replace, replaceOrAppend, retry, select, series, set, shake, shift, shuffle, sift, sleep, snake, sort, sum, template, throttle, title, toFloat, toInt, toggle, trim, tryit as try, tryit, uid, unique, upperize, zip, zipToObject };

Diff for: cdn/radash.js

+23
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,28 @@ var radash = (function (exports) {
411411
}
412412
return results.map((r) => r.result);
413413
};
414+
async function all(promises) {
415+
const entries = isArray(promises) ? promises.map((p) => [null, p]) : Object.entries(promises);
416+
const results = await Promise.all(
417+
entries.map(
418+
([key, value]) => value.then((result) => ({ result, exc: null, key })).catch((exc) => ({ result: null, exc, key }))
419+
)
420+
);
421+
const exceptions = results.filter((r) => r.exc);
422+
if (exceptions.length > 0) {
423+
throw new AggregateError(exceptions.map((e) => e.exc));
424+
}
425+
if (isArray(promises)) {
426+
return results.map((r) => r.result);
427+
}
428+
return results.reduce(
429+
(acc, item) => ({
430+
...acc,
431+
[item.key]: item.result
432+
}),
433+
{}
434+
);
435+
}
414436
const retry = async (options, func) => {
415437
const times = options?.times ?? 3;
416438
const delay = options?.delay;
@@ -891,6 +913,7 @@ var radash = (function (exports) {
891913
return str.replace(regex, "");
892914
};
893915

916+
exports.all = all;
894917
exports.alphabetical = alphabetical;
895918
exports.assign = assign;
896919
exports.boil = boil;

Diff for: cdn/radash.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "radash",
3-
"version": "10.8.1",
3+
"version": "10.9.0",
44
"description": "Functional utility library - modern, simple, typed, powerful",
55
"main": "dist/cjs/index.cjs",
66
"module": "dist/esm/index.mjs",

Diff for: src/async.ts

+70
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { fork, list, range, sort } from './array'
2+
import { isArray } from './typed'
23

34
/**
45
* An async reduce function. Works like the
@@ -145,6 +146,75 @@ export const parallel = async <T, K>(
145146
return results.map(r => r.result)
146147
}
147148

149+
type PromiseValues<T extends Promise<any>[]> = {
150+
[K in keyof T]: T[K] extends Promise<infer U> ? U : never
151+
}
152+
153+
/**
154+
* Functionally similar to Promise.all or Promise.allSettled. If any
155+
* errors are thrown, all errors are gathered and thrown in an
156+
* AggregateError.
157+
*
158+
* @example
159+
* const [user] = await all({
160+
* api.users.create(...),
161+
* s3.buckets.create(...),
162+
* slack.customerSuccessChannel.sendMessage(...)
163+
* })
164+
*/
165+
export async function all<T extends Promise<any>[]>(
166+
promises: T
167+
): Promise<PromiseValues<T>>
168+
/**
169+
* Functionally similar to Promise.all or Promise.allSettled. If any
170+
* errors are thrown, all errors are gathered and thrown in an
171+
* AggregateError.
172+
*
173+
* @example
174+
* const { user } = await all({
175+
* user: api.users.create(...),
176+
* bucket: s3.buckets.create(...),
177+
* message: slack.customerSuccessChannel.sendMessage(...)
178+
* })
179+
*/
180+
export async function all<T extends Record<string, Promise<any>>>(
181+
promises: T
182+
): Promise<{ [K in keyof T]: Awaited<T[K]> }>
183+
export async function all<
184+
T extends Record<string, Promise<any>> | Promise<any>[]
185+
>(promises: T) {
186+
const entries = isArray(promises)
187+
? promises.map(p => [null, p] as [null, Promise<any>])
188+
: Object.entries(promises)
189+
190+
const results = await Promise.all(
191+
entries.map(([key, value]) =>
192+
value
193+
.then(result => ({ result, exc: null, key }))
194+
.catch(exc => ({ result: null, exc, key }))
195+
)
196+
)
197+
198+
const exceptions = results.filter(r => r.exc)
199+
if (exceptions.length > 0) {
200+
throw new AggregateError(exceptions.map(e => e.exc))
201+
}
202+
203+
if (isArray(promises)) {
204+
return results.map(r => r.result) as T extends Promise<any>[]
205+
? PromiseValues<T>
206+
: unknown
207+
}
208+
209+
return results.reduce(
210+
(acc, item) => ({
211+
...acc,
212+
[item.key!]: item.result
213+
}),
214+
{} as { [K in keyof T]: Awaited<T[K]> }
215+
)
216+
}
217+
148218
/**
149219
* Retries the given function the specified number
150220
* of times.

Diff for: src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export {
3030
zipToObject
3131
} from './array'
3232
export {
33+
all,
3334
defer,
3435
guard,
3536
map,

Diff for: src/tests/async.test.ts

+57
Original file line numberDiff line numberDiff line change
@@ -485,4 +485,61 @@ describe('async module', () => {
485485
}
486486
})
487487
})
488+
489+
describe('_.all', () => {
490+
const promise = {
491+
pass: <T>(value: T) => new Promise<T>(res => res(value)),
492+
fail: (err: any) => new Promise((res, rej) => rej(err))
493+
}
494+
it('returns array with values in correct order when given array', async () => {
495+
const result = await _.all([
496+
promise.pass(22),
497+
promise.pass('hello'),
498+
promise.pass({ name: 'ray' })
499+
])
500+
assert.deepEqual(result, [22, 'hello', { name: 'ray' }])
501+
})
502+
it('returns object with values in correct keys when given object', async () => {
503+
const result = await _.all({
504+
num: promise.pass(22),
505+
str: promise.pass('hello'),
506+
obj: promise.pass({ name: 'ray' })
507+
})
508+
assert.deepEqual(result, {
509+
num: 22,
510+
str: 'hello',
511+
obj: { name: 'ray' }
512+
})
513+
})
514+
it('throws aggregate error when a single promise fails (in object mode)', async () => {
515+
try {
516+
await _.all({
517+
num: promise.pass(22),
518+
str: promise.pass('hello'),
519+
err: promise.fail(new Error('broken'))
520+
})
521+
} catch (e: any) {
522+
const err = e as AggregateError
523+
assert.equal(err.errors.length, 1)
524+
assert.equal(err.errors[0].message, 'broken')
525+
return
526+
}
527+
assert.fail('Expected error to be thrown but it was not')
528+
})
529+
it('throws aggregate error when a single promise fails (in array mode)', async () => {
530+
try {
531+
await _.all([
532+
promise.pass(22),
533+
promise.pass('hello'),
534+
promise.fail(new Error('broken'))
535+
])
536+
} catch (e: any) {
537+
const err = e as AggregateError
538+
assert.equal(err.errors.length, 1)
539+
assert.equal(err.errors[0].message, 'broken')
540+
return
541+
}
542+
assert.fail('Expected error to be thrown but it was not')
543+
})
544+
})
488545
})

0 commit comments

Comments
 (0)