From 4a244d4dd8c895ab483ff58ff9bae5a988e6e9ba Mon Sep 17 00:00:00 2001 From: Ivan Koryakovtsev Date: Mon, 12 Sep 2022 22:58:55 +0300 Subject: [PATCH] feat: add toListLoader utility and deprecate toListDataLoader --- .gitignore | 2 +- packages/cache/src/index.ts | 1 + packages/cache/src/key.test.ts | 32 +++++++++++++++++++++++++++++++- packages/cache/src/key.ts | 27 +++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 77436d3..0167fe7 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,4 @@ npm-debug.* .classpath .project .settings - +.yarn-error.log \ No newline at end of file diff --git a/packages/cache/src/index.ts b/packages/cache/src/index.ts index 9c925f7..73318b8 100644 --- a/packages/cache/src/index.ts +++ b/packages/cache/src/index.ts @@ -17,6 +17,7 @@ export { byKeyWithDefaultFactory, byKey, toListDataLoader, + toListLoader, } from "./key" export { toWrapped, toCache } from "./utils" export { Memo, MemoImpl } from "./memo" diff --git a/packages/cache/src/key.test.ts b/packages/cache/src/key.test.ts index 161bca2..872cc31 100644 --- a/packages/cache/src/key.test.ts +++ b/packages/cache/src/key.test.ts @@ -3,7 +3,7 @@ import { Map as IM } from "immutable" import waitForExpect from "wait-for-expect" import { createFulfilledWrapped, pendingWrapped, Rejected, Wrapped } from "@rixio/wrapped" import { waitFor } from "@testing-library/react" -import { KeyCacheImpl, toListDataLoader } from "./key" +import { KeyCacheImpl, toListLoader, toListDataLoader } from "./key" import { createAddKeyEvent, createErrorKeyEvent, KeyEvent } from "./domain" import { CacheState, createFulfilledCache } from "./index" @@ -123,3 +123,33 @@ describe("KeyCacheImpl", () => { }) }) }) + +describe("toListLoader", () => { + it("should resolve all values", async () => { + const loader = toListLoader(x => Promise.resolve(x + "1"), undefined) + const result = await loader(["hello", "world"]) + expect(result).toStrictEqual([ + ["hello", "hello1"], + ["world", "world1"], + ]) + }) + + it("should return default value if one element fails", async () => { + const logger = jest.fn() + const err = new Error("My error") + const loader = toListLoader( + x => { + if (x === "hello") return Promise.reject(err) + return Promise.resolve(x + "1") + }, + undefined, + logger + ) + const result = await loader(["hello", "world"]) + expect(result).toStrictEqual([ + ["hello", undefined], + ["world", "world1"], + ]) + expect(logger.mock.calls[0]).toEqual(["hello", err]) + }) +}) diff --git a/packages/cache/src/key.ts b/packages/cache/src/key.ts index 657e01c..be2560c 100644 --- a/packages/cache/src/key.ts +++ b/packages/cache/src/key.ts @@ -21,10 +21,37 @@ export type DataLoader = (key: K) => Promise export type ListDataLoader = (keys: K[]) => Promise<[K, V][]> +/** + * @deprecated please use toListLoader + * since it can handle errors and doesn't trigger fail of whole chain + */ + export function toListDataLoader(loader: DataLoader): ListDataLoader { return ids => Promise.all(ids.map(id => loader(id).then(v => [id, v] as [K, V]))) } +/** + * Utility to conver your single-loader to list loader + */ + +export function toListLoader( + loader: DataLoader, + defaultValue: J, + onError?: (id: K, error: unknown) => void +): ListDataLoader { + return ids => + Promise.all( + ids.map(id => + loader(id) + .then(v => [id, v] as [K, V]) + .catch(err => { + onError?.(id, err) + return [id, defaultValue] as [K, J] + }) + ) + ) +} + export interface KeyCache { get(key: K, force?: boolean): Promise set(key: K, value: V): void