Skip to content

Commit c86696b

Browse files
authored
feat: add resolveProps (#340) (#343)
* feat: add `resolveProps` (#340) * docs: fix resolveProps docs * feat: add type test for resolveProps
1 parent cf68fc1 commit c86696b

File tree

4 files changed

+142
-2
lines changed

4 files changed

+142
-2
lines changed

src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import prop from "./prop";
5353
import props from "./props";
5454
import reduce from "./reduce";
5555
import reduceLazy from "./reduceLazy";
56+
import resolveProps from "./resolveProps";
5657
import size from "./size";
5758
import some from "./some";
5859
import sort from "./sort";
@@ -98,11 +99,11 @@ export {
9899
isDate,
99100
isEmpty,
100101
isNil,
102+
isNull,
101103
isNumber,
102104
isObject,
103105
isString,
104106
isUndefined,
105-
isNull,
106107
join,
107108
juxt,
108109
last,
@@ -126,8 +127,8 @@ export {
126127
props,
127128
reduce,
128129
reduceLazy,
130+
resolveProps,
129131
size,
130-
unless,
131132
some,
132133
sort,
133134
sortBy,
@@ -137,6 +138,7 @@ export {
137138
throwIf,
138139
toArray,
139140
unicodeToArray,
141+
unless,
140142
when,
141143
};
142144

src/resolveProps.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Resolve all properties of an object that may be promises.
3+
*
4+
* @example
5+
* ```ts
6+
* const obj = {
7+
* a: Promise.resolve(1),
8+
* b: 2,
9+
* c: Promise.resolve(3),
10+
* }
11+
*
12+
* const result = await resolveProps(obj);
13+
* console.log(result); // { a: 1, b: 2, c: 3 }
14+
* ```
15+
*
16+
* Also, with this function, you can define `asyncEvolve` simply.
17+
*
18+
* ```ts
19+
* import { evolve, pipe, resolveProps } from "@fx-ts/core";
20+
*
21+
* const asyncEvolve = <T extends object>(transformation:Transformation<T>) =>
22+
* (obj:T) => pipe(obj, evolve(transformation), resolveProps)
23+
*
24+
* const a = await asyncEvolve({ a: async (a) => a + 1 })({ a: 1 });
25+
* console.log(a); // { a: 2 }
26+
*
27+
* const resultByFormat = await asyncEvolve<Request>({
28+
* foo: async (foo) => await fetch(`https://example.com/foo/${foo}`).json(),
29+
* bar: async (bar) => await fetch(`https://example.com/bar/${bar}`).text(),
30+
* })
31+
* const result = await resultByFormat({foo: "foo", bar: "bar"});
32+
*
33+
* console.log({
34+
* foo: result.foo,
35+
* bar: result.bar,
36+
* })
37+
* ```
38+
*
39+
* see {@link https://fxts.dev/docs/pipe | pipe} and {@link https://fxts.dev/docs/evolve | evolve}.
40+
*
41+
* @typeParam T - Type of input object
42+
* @typeParam Key - Alias type of Keys of input object which are not symbols
43+
*/
44+
async function resolveProps<T extends object, Key = Exclude<keyof T, symbol>>(
45+
obj: T,
46+
): Promise<{ [K in keyof T]: K extends Key ? Awaited<T[K]> : never }> {
47+
return await Promise.all(
48+
Object.entries(obj).map(async ([k, v]) => [k, await v]),
49+
).then(Object.fromEntries);
50+
}
51+
52+
export default resolveProps;

test/resolveProps.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import resolveProps from "../src/resolveProps";
2+
3+
describe("resolveProps", () => {
4+
it("should resolve an object of promises to a promise of an object", async () => {
5+
const obj = {
6+
a: Promise.resolve(1),
7+
b: Promise.resolve("2"),
8+
c: Promise.resolve(true),
9+
d: "non-promise value",
10+
};
11+
12+
const result = await resolveProps(obj);
13+
expect(result).toEqual({ a: 1, b: "2", c: true, d: "non-promise value" });
14+
});
15+
});

type-check/resolveProps.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { evolve, pipe, resolveProps } from "../src";
2+
import * as Test from "../src/types/Test";
3+
4+
const { checks, check } = Test;
5+
6+
const res1 = pipe(
7+
{
8+
1: Promise.resolve(1),
9+
a: Promise.resolve("a"),
10+
undefined: Promise.resolve(undefined),
11+
null: Promise.resolve(null),
12+
bool: Promise.resolve(true),
13+
object: Promise.resolve({ key: "value" }),
14+
},
15+
resolveProps,
16+
);
17+
const res2 = pipe(
18+
{
19+
notPromiseProp: 1,
20+
promiseProp: Promise.resolve(1),
21+
},
22+
resolveProps,
23+
);
24+
const res3 = pipe(
25+
{
26+
notPromiseAsync: 1,
27+
notPromiseResolve: 1,
28+
promiseAsync: Promise.resolve(1),
29+
promiseThen: Promise.resolve(1),
30+
},
31+
evolve({
32+
notPromiseAsync: async (x) => ({ a: x }),
33+
notPromiseResolve: (x) => Promise.resolve({ b: x + 1 }),
34+
promiseAsync: async (x) => ({ c: (await x) + 2 }),
35+
promiseThen: (x) => x.then((d) => ({ d: d + 3 })),
36+
}),
37+
resolveProps,
38+
);
39+
40+
checks([
41+
check<
42+
typeof res1,
43+
Promise<{
44+
1: number;
45+
a: string;
46+
undefined: undefined;
47+
null: null;
48+
bool: boolean;
49+
object: { key: string };
50+
}>,
51+
Test.Pass
52+
>(),
53+
check<
54+
typeof res2,
55+
Promise<{
56+
notPromiseProp: number;
57+
promiseProp: number;
58+
}>,
59+
Test.Pass
60+
>(),
61+
check<
62+
typeof res3,
63+
Promise<{
64+
notPromiseAsync: { a: number };
65+
notPromiseResolve: { b: number };
66+
promiseAsync: { c: number };
67+
promiseThen: { d: number };
68+
}>,
69+
Test.Pass
70+
>(),
71+
]);

0 commit comments

Comments
 (0)