diff --git a/packages/memoize/src/memoize.ts b/packages/memoize/src/memoize.ts index 4880b42918..05c4878cc0 100644 --- a/packages/memoize/src/memoize.ts +++ b/packages/memoize/src/memoize.ts @@ -1,4 +1,4 @@ -import type { Fn, Fn2, Fn3, Fn4, FnAny } from "@thi.ng/api"; +import type { FnAny } from "@thi.ng/api"; import type { MapLike } from "./api.js"; /** @@ -18,20 +18,11 @@ import type { MapLike } from "./api.js"; * @param fn - * @param cache - */ -export function memoize(fn: Fn, cache: MapLike): Fn; -export function memoize( - fn: Fn2, - cache: MapLike<[A, B], C> -): Fn2; -export function memoize( - fn: Fn3, - cache: MapLike<[A, B, C], D> -): Fn3; -export function memoize( - fn: Fn4, - cache: MapLike<[A, B, C, D], E> -): Fn4; -export function memoize(fn: FnAny, cache: MapLike): FnAny { +export function memoize>( + fn: T, + cache: MapLike +): T { + // @ts-ignore return (...args: any[]) => { let res; return cache.has(args) @@ -39,3 +30,22 @@ export function memoize(fn: FnAny, cache: MapLike): FnAny { : (cache.set(args, (res = fn.apply(null, args))), res); }; } + +/** + * Async version of {@link memoize}. + * + * @param fn + * @param cache + */ +export function memoizeAsync>( + fn: T, + cache: MapLike +): (...xs: Parameters) => Promise>> { + // @ts-ignore + return async (...args: any[]) => { + let res; + return cache.has(args) + ? cache.get(args) + : (cache.set(args, (res = await fn.apply(null, args))), res); + }; +} diff --git a/packages/memoize/src/memoize1.ts b/packages/memoize/src/memoize1.ts index 105f6d13bd..80cbf8c0f3 100644 --- a/packages/memoize/src/memoize1.ts +++ b/packages/memoize/src/memoize1.ts @@ -1,4 +1,4 @@ -import type { Fn } from "@thi.ng/api"; +import type { Fn, MaybePromise } from "@thi.ng/api"; import type { MapLike } from "./api.js"; /** @@ -24,3 +24,18 @@ export const memoize1 = ? cache.get(x)! : (cache.set(x, (res = fn(x))), res); }; + +/** + * Async version of {@link memoize1}. + * + * @param fn + * @param cache + */ +export const memoizeAsync1 = + (fn: Fn>, cache: MapLike = new Map()) => + async (x: A): Promise => { + let res; + return cache.has(x) + ? cache.get(x)! + : (cache.set(x, (res = await fn(x))), res); + }; diff --git a/packages/memoize/src/memoizej.ts b/packages/memoize/src/memoizej.ts index fc1cd250ce..dff1d527d7 100644 --- a/packages/memoize/src/memoizej.ts +++ b/packages/memoize/src/memoizej.ts @@ -1,4 +1,4 @@ -import type { Fn, Fn2, Fn3, Fn4, FnAny, IObjectOf } from "@thi.ng/api"; +import type { FnAny, IObjectOf } from "@thi.ng/api"; /** * Function memoization for arbitrary argument counts. Returns augmented @@ -15,23 +15,11 @@ import type { Fn, Fn2, Fn3, Fn4, FnAny, IObjectOf } from "@thi.ng/api"; * @param fn - * @param cache - */ -export function memoizeJ(fn: Fn, cache?: IObjectOf): Fn; -export function memoizeJ( - fn: Fn2, - cache?: IObjectOf -): Fn2; -export function memoizeJ( - fn: Fn3, - cache?: IObjectOf -): Fn3; -export function memoizeJ( - fn: Fn4, - cache?: IObjectOf -): Fn4; -export function memoizeJ( - fn: FnAny, - cache: Record = Object.create(null) -): FnAny { +export function memoizeJ>( + fn: T, + cache: IObjectOf = Object.create(null) +): T { + // @ts-ignore return (...args: any[]) => { const key = JSON.stringify(args); if (key !== undefined) { @@ -42,3 +30,25 @@ export function memoizeJ( return fn.apply(null, args); }; } + +/** + * Async version of {@link memoizeJ}. + * + * @param fn + * @param cache + */ +export function memoizeAsyncJ>( + fn: T, + cache: IObjectOf = Object.create(null) +): (...xs: Parameters) => Promise>> { + // @ts-ignore + return async (...args: any[]) => { + const key = JSON.stringify(args); + if (key !== undefined) { + return key in cache + ? cache[key] + : (cache[key] = await fn.apply(null, args)); + } + return await fn.apply(null, args); + }; +} diff --git a/packages/memoize/src/memoizeo.ts b/packages/memoize/src/memoizeo.ts index 025cea61f1..ea21b1bb55 100644 --- a/packages/memoize/src/memoizeo.ts +++ b/packages/memoize/src/memoizeo.ts @@ -1,12 +1,24 @@ -import type { Fn, Fn2, Fn3, Fn4, NumOrString } from "@thi.ng/api"; +import type { + Fn, + Fn2, + Fn3, + Fn4, + IObjectOf, + MaybePromise, + NumOrString, +} from "@thi.ng/api"; /** - * The most minimalistic & fastest memoization function of this package. Similar - * to {@link memoize1}, but only supports numbers or strings as keys and uses a - * vanilla JS object as cache. + * The most minimalistic memoization function of this package, but only supports + * numbers or strings as arguments (max. 4) and uses a vanilla JS object as + * cache. * * @remarks - * Also see {@link memoize1}, {@link memoizeJ}, {@link memoize}. + * If `fn` throws an error, no result value will be cached and no memoization + * happens for this invocation using the given arguments. + * + * Use {@link memoizeAsyncO} for async functions or other functions returning + * promises. * * @example * ```ts tangle:../export/memoizeo.ts @@ -29,64 +41,82 @@ import type { Fn, Fn2, Fn3, Fn4, NumOrString } from "@thi.ng/api"; * @param fn * @param cache */ -export const memoizeO = - ( - fn: Fn, - cache: Record = Object.create(null) - ) => - (x: A): B => - x in cache ? cache[x] : (cache[x] = fn(x)); - -/** - * Like {@link memoizeO}, but for functions with 2 arguments. - * - * @param fn - * @param cache - */ -export const memoize2O = - ( - fn: Fn2, - cache: Record = Object.create(null) - ) => - (a: A, b: B): C => { - const key = a + "-" + b; - return key in cache ? cache[key] : (cache[key] = fn(a, b)); +export function memoizeO( + fn: Fn, + cache?: IObjectOf +): Fn; +export function memoizeO( + fn: Fn2, + cache?: IObjectOf +): Fn2; +export function memoizeO< + A extends NumOrString, + B extends NumOrString, + C extends NumOrString, + D +>(fn: Fn3, cache?: IObjectOf): Fn3; +export function memoizeO< + A extends NumOrString, + B extends NumOrString, + C extends NumOrString, + D extends NumOrString, + E +>(fn: Fn4, cache?: IObjectOf): Fn4; +export function memoizeO any>( + fn: T, + cache: IObjectOf> = Object.create(null) +): T { + // @ts-ignore + return (...xs: any[]) => { + const key = xs.join("-"); + return key in cache ? cache[key] : (cache[key] = fn(...xs)); }; +} /** - * Like {@link memoizeO}, but for functions with 3 arguments. + * Async version of {@link memoizeO}. * - * @param fn - * @param cache - */ -export const memoize3O = - ( - fn: Fn3, - cache: Record = Object.create(null) - ) => - (a: A, b: B, c: C): D => { - const key = a + "-" + b + "-" + c; - return key in cache ? cache[key] : (cache[key] = fn(a, b, c)); - }; - -/** - * Like {@link memoizeO}, but for functions with 4 arguments. + * @remarks + * If `fn` throws an error, no result value will be cached and no memoization + * happens for this invocation using the given arguments. * * @param fn * @param cache */ -export const memoize4O = - < - A extends NumOrString, - B extends NumOrString, - C extends NumOrString, - D extends NumOrString, - E - >( - fn: Fn4, - cache: Record = Object.create(null) - ) => - (a: A, b: B, c: C, d: D): E => { - const key = a + "-" + b + "-" + c + "-" + d; - return key in cache ? cache[key] : (cache[key] = fn(a, b, c, d)); +export function memoizeAsyncO( + fn: Fn>, + cache?: IObjectOf +): Fn>; +export function memoizeAsyncO( + fn: Fn2>, + cache?: IObjectOf +): Fn2>; +export function memoizeAsyncO< + A extends NumOrString, + B extends NumOrString, + C extends NumOrString, + D +>( + fn: Fn3>, + cache?: IObjectOf +): Fn3>; +export function memoizeAsyncO< + A extends NumOrString, + B extends NumOrString, + C extends NumOrString, + D extends NumOrString, + E +>( + fn: Fn4>, + cache?: IObjectOf +): Fn4>; +export function memoizeAsyncO any>( + fn: T, + cache: IObjectOf> = Object.create(null) +): T { + // @ts-ignore + return async (...xs: any[]) => { + const key = xs.join("-"); + return key in cache ? cache[key] : (cache[key] = await fn(...xs)); }; +} diff --git a/packages/memoize/test/main.test.ts b/packages/memoize/test/main.test.ts index aa548208a2..3f6f3c322c 100644 --- a/packages/memoize/test/main.test.ts +++ b/packages/memoize/test/main.test.ts @@ -1,7 +1,7 @@ import { EquivMap } from "@thi.ng/associative"; import { LRUCache } from "@thi.ng/cache"; import { expect, test } from "bun:test"; -import { memoize1, memoize2O, memoizeO } from "../src/index.js"; +import { memoize1, memoizeO } from "../src/index.js"; test("memoize1", () => { const calls: number[] = []; @@ -16,7 +16,7 @@ test("memoize1", () => { test("memoizeO", () => { const calls: number[] = []; - const f = memoizeO((x) => (calls.push(x), x * 10)); + const f = memoizeO((x: number) => (calls.push(x), x * 10)); expect(f(1)).toBe(10); expect(f(2)).toBe(20); expect(f(2)).toBe(20); @@ -27,8 +27,10 @@ test("memoizeO", () => { test("memoize2O", () => { const calls: number[][] = []; - const f = memoize2O( - (a, b) => (calls.push([a, b]), a * b) + const cache: Record = {}; + const f = memoizeO( + (a: number, b: number) => (calls.push([a, b]), a * b), + cache ); expect(f(1, 2)).toBe(2); expect(f(1, 2)).toBe(2); @@ -39,6 +41,11 @@ test("memoize2O", () => { [2, 3], [2, 1], ]); + expect(cache).toEqual({ + "1-2": 2, + "2-1": 2, + "2-3": 6, + }); }); test("memoize1 (equivmap)", () => {