feat: emit errors as part of fetchFromMultipleSources
This commit is contained in:
@@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user