From 882ec3ccbe5c460a9d4e5fe4c3717af0e2b6ac40 Mon Sep 17 00:00:00 2001 From: Benji Grant Date: Tue, 12 Sep 2023 20:23:44 +1000 Subject: [PATCH 1/2] Add settleObject util --- .changeset/lucky-snakes-flow.md | 5 +++ .changeset/modern-doors-melt.md | 5 +++ lib/promises/resolveObject.ts | 18 +++++----- lib/promises/settleObject.ts | 61 +++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 .changeset/lucky-snakes-flow.md create mode 100644 .changeset/modern-doors-melt.md create mode 100644 lib/promises/settleObject.ts diff --git a/.changeset/lucky-snakes-flow.md b/.changeset/lucky-snakes-flow.md new file mode 100644 index 0000000..3da1db5 --- /dev/null +++ b/.changeset/lucky-snakes-flow.md @@ -0,0 +1,5 @@ +--- +"@giraugh/tools": minor +--- + +Add a `settleObject` util that behaves like Promise.allSettled for objects diff --git a/.changeset/modern-doors-melt.md b/.changeset/modern-doors-melt.md new file mode 100644 index 0000000..1af9fc2 --- /dev/null +++ b/.changeset/modern-doors-melt.md @@ -0,0 +1,5 @@ +--- +"@giraugh/tools": patch +--- + +Allow `resolveObject` to take a `number` or `symbol` as a key diff --git a/lib/promises/resolveObject.ts b/lib/promises/resolveObject.ts index 9049cdd..9790a37 100644 --- a/lib/promises/resolveObject.ts +++ b/lib/promises/resolveObject.ts @@ -2,27 +2,29 @@ export type Resolved = { [k in keyof Type]: Awaited } /** * Resolve all of the fields of an object in parallel - * @param object An object where every field is a promise + * @param object An object where every field is a promise * @returns The same object with every field resolved - * + * * @example await resolveObject({ * a: Promise.resolve('a'), - * b: Promise.resolve('b'), + * b: Promise.resolve('b'), * }) === { a: 'a', b: 'b' } * * @example await resolveObject({ * a: Promise.resolve('a'), - * b: Promise.reject('b'), + * b: Promise.reject('b'), * }) // throws error + * + * @see {@link settleObject} if you don't want to fail if some promises reject */ -export const resolveObject = async >>(object: T): Promise> => { - // Create promises for each entry +export const resolveObject = async >>(object: T): Promise> => { + // Create promises for each entry const entryPromises = Object.entries(object) .map(([key, promise]) => promise.then(value => [key, value])) - + // Resolve the promises const resolvedEntries = await Promise.all(entryPromises) - + // Reconstruct object return Object.fromEntries(resolvedEntries) } diff --git a/lib/promises/settleObject.ts b/lib/promises/settleObject.ts new file mode 100644 index 0000000..f40335b --- /dev/null +++ b/lib/promises/settleObject.ts @@ -0,0 +1,61 @@ +/** + * Settle all of the fields of an object in parallel + * @param object An object where every field is a promise + * @returns The same object with every field settled + * + * @example await settleObject({ + * a: Promise.resolve('a'), + * b: Promise.resolve('b'), + * }) === { + * a: { status: 'fulfilled', value: 'a' }, + * b: { status: 'fulfilled', value: 'b' }, + * } + * + * @example await settleObject({ + * a: Promise.resolve('a'), + * b: Promise.reject('b'), + * }) === { + * a: { status: 'fulfilled', value: 'a' }, + * b: { status: 'rejected', reason: 'b' }, + * } + * + * @see {@link resolveObject} if you want to fail on any promise rejection + */ +export const settleObject = async >>(object: T): Promise<{ [k in keyof T]: PromiseSettledResult }> => { + // Turn into entries + const entries = Object.entries(object) + + // Settle the promises + const settledValues = await Promise.allSettled(entries.map(e => e[1])) + + // Match keys with settled values + const settledEntries = settledValues.map((v, i) => [entries[i][0], v] as const) + + // Reconstruct object + return Object.fromEntries(settledEntries) as { [k in keyof T]: PromiseSettledResult } +} + +// Tests +if (import.meta.vitest) { + const { it, expect } = import.meta.vitest + + it('works for example 1', () => { + expect(settleObject({ + a: Promise.resolve('a'), + b: Promise.resolve('b'), + })).resolves.toStrictEqual({ + a: { status: 'fulfilled', value: 'a' }, + b: { status: 'fulfilled', value: 'b' }, + }) + }) + + it('works for example 2', () => { + expect(settleObject({ + a: Promise.resolve('a'), + b: Promise.reject('b'), + })).resolves.toStrictEqual({ + a: { status: 'fulfilled', value: 'a' }, + b: { status: 'rejected', reason: 'b' }, + }) + }) +} From 7c19f22d71bbffdf8367b911973813d10568b15b Mon Sep 17 00:00:00 2001 From: Benji Grant Date: Tue, 12 Sep 2023 20:26:46 +1000 Subject: [PATCH 2/2] Export new util from barrel file --- lib/promises/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/promises/index.ts b/lib/promises/index.ts index 6afa6f8..69f90dd 100644 --- a/lib/promises/index.ts +++ b/lib/promises/index.ts @@ -1,3 +1,4 @@ export * from './filterAsync' export * from './mapAsync' export * from './resolveObject' +export * from './settleObject'