feat: emit errors as part of fetchFromMultipleSources

This commit is contained in:
2024-06-07 22:39:30 -04:00
parent 4812e56296
commit 6fd2cc4feb
2 changed files with 86 additions and 21 deletions

View File

@@ -9,35 +9,56 @@ describe("fetchFromMultipleSources", () => {
); );
}); });
it("has promises, returns first one with value", async () => { it("has promises with valid responses, returns first one with value", async () => {
const actual = await fetchFromMultipleSources<number>([ const { result } = await fetchFromMultipleSources<number>([
() => Promise.resolve(undefined), () => Promise.resolve(undefined),
() => Promise.resolve(null), () => Promise.resolve(null),
() => Promise.reject(), () => Promise.reject(),
() => Promise.resolve(2), () => Promise.resolve(2),
() => Promise.resolve(3), () => Promise.resolve(3),
]); ]);
const expected = 2;
expect(actual).toEqual(expected); expect(result).toEqual(2);
}); });
it("has promises, no valid responses, returns null", async () => { it("has promises with valid responses, contains no errors", async () => {
const actual = await fetchFromMultipleSources<number>([ const { errors } = await fetchFromMultipleSources<number>([
() => Promise.resolve(undefined),
() => Promise.resolve(null), () => Promise.resolve(null),
() => Promise.reject(), () => Promise.reject(),
() => Promise.resolve(undefined), () => Promise.resolve(2),
() => Promise.resolve(3),
]); ]);
const expected = null;
expect(actual).toBe(expected); expect(errors).toBeNull();
}); });
it("has promises, has cached value, returns cached value", async () => { it("has promises with no valid responses, returns null", async () => {
const actual = await fetchFromMultipleSources<number>( const { result } = await fetchFromMultipleSources<number>([
() => Promise.resolve(null),
() => Promise.reject("error"),
() => Promise.resolve(undefined),
]);
expect(result).toBeNull();
});
it("has promises with no valid responses, contains error", async () => {
const { errors } = await fetchFromMultipleSources<number>([
() => Promise.resolve(null),
() => Promise.reject("error"),
() => Promise.reject(new Error("error")),
() => Promise.resolve(undefined),
]);
expect(errors).toEqual(["error", new Error("error")]);
});
it("has promises but cache has value, returns cached value", async () => {
const { result } = await fetchFromMultipleSources<number>(
[ [
() => Promise.resolve(null), () => Promise.resolve(null),
() => Promise.reject(), () => Promise.reject("error"),
() => Promise.resolve(undefined), () => Promise.resolve(undefined),
], ],
{ {
@@ -45,9 +66,24 @@ describe("fetchFromMultipleSources", () => {
saveInCache: async () => {}, saveInCache: async () => {},
}, },
); );
const expected = -1;
expect(actual).toBe(expected); expect(result).toEqual(-1);
});
it("has promises but cache has value, contains no errors", async () => {
const { errors } = await fetchFromMultipleSources<number>(
[
() => Promise.resolve(null),
() => Promise.reject("error"),
() => Promise.resolve(undefined),
],
{
fetchFromCache: () => Promise.resolve(-1),
saveInCache: async () => {},
},
);
expect(errors).toBeNull();
}); });
it("has promises, no cached value, no valid response, should not save in cache", async () => { it("has promises, no cached value, no valid response, should not save in cache", async () => {
@@ -65,9 +101,8 @@ describe("fetchFromMultipleSources", () => {
}, },
}, },
); );
const expected = undefined;
expect(actual).toBe(expected); expect(actual).toBeUndefined();
}); });
it("has promises, no cached value, has valid response, should save in cache", async () => { it("has promises, no cached value, has valid response, should save in cache", async () => {
@@ -89,4 +124,20 @@ describe("fetchFromMultipleSources", () => {
expect(actual).toBe(expected); expect(actual).toBe(expected);
}); });
it("has promises, no cached value, has valid response, value that was cached is returned", async () => {
const { result } = await fetchFromMultipleSources(
[
() => Promise.resolve(null),
() => Promise.reject(),
() => Promise.resolve(3),
],
{
fetchFromCache: () => Promise.resolve(null),
saveInCache: async (value) => {},
},
);
expect(result).toBe(3);
});
}); });

View File

@@ -8,29 +8,43 @@ type OptionalArgs<T> =
fetchFromCache?: undefined; fetchFromCache?: undefined;
}; };
interface FetchFromMultipleSourcesResult<T> {
result: T | null;
errors: (Error | string | undefined)[] | null;
}
export async function fetchFromMultipleSources<T>( export async function fetchFromMultipleSources<T>(
fetchPromises: (() => Promise<T | null | undefined>)[], fetchPromises: (() => Promise<T | null | undefined>)[],
args?: OptionalArgs<T>, args?: OptionalArgs<T>,
) { ): Promise<FetchFromMultipleSourcesResult<T>> {
let result = await args?.fetchFromCache?.(); let result = await args?.fetchFromCache?.();
if (result) { if (result) {
return result; return { result, errors: null };
} }
if (fetchPromises.length === 0) { if (fetchPromises.length === 0) {
throw new Error("fetchPromises cannot be empty"); throw new Error("fetchPromises cannot be empty");
} }
for (const promise of fetchPromises) { let errors: Record<number, Error> = {};
for (let i = 0; i < fetchPromises.length; i++) {
const promise = fetchPromises[i];
try { try {
result = await promise(); result = await promise();
if (result) break; if (result) break;
} catch (e) {} } catch (e) {
errors[i] = e as Error;
}
} }
if (args?.saveInCache && result) { if (args?.saveInCache && result) {
await args.saveInCache(result); await args.saveInCache(result);
} }
return result ?? null; result = result ?? null;
return {
result,
errors:
!result && Object.keys(errors).length > 0 ? Object.values(errors) : null,
};
} }