mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 19:20:42 +00:00
refactor: unify lazy module loaders
This commit is contained in:
59
src/shared/lazy-promise.test.ts
Normal file
59
src/shared/lazy-promise.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createLazyImportLoader, createLazyPromiseLoader } from "./lazy-promise.js";
|
||||
|
||||
describe("createLazyPromiseLoader", () => {
|
||||
it("dedupes concurrent loads and reuses the resolved value", async () => {
|
||||
let calls = 0;
|
||||
const loader = createLazyPromiseLoader(async () => `loaded-${++calls}`);
|
||||
|
||||
await expect(Promise.all([loader.load(), loader.load()])).resolves.toEqual([
|
||||
"loaded-1",
|
||||
"loaded-1",
|
||||
]);
|
||||
await expect(loader.load()).resolves.toBe("loaded-1");
|
||||
expect(calls).toBe(1);
|
||||
});
|
||||
|
||||
it("evicts rejected loads by default so retries can recover", async () => {
|
||||
let calls = 0;
|
||||
const loader = createLazyPromiseLoader(async () => {
|
||||
calls += 1;
|
||||
if (calls === 1) {
|
||||
throw new Error("transient");
|
||||
}
|
||||
return "recovered";
|
||||
});
|
||||
|
||||
await expect(loader.load()).rejects.toThrow("transient");
|
||||
await expect(loader.load()).resolves.toBe("recovered");
|
||||
expect(calls).toBe(2);
|
||||
});
|
||||
|
||||
it("can keep rejected loads when requested", async () => {
|
||||
const load = vi.fn(async () => {
|
||||
throw new Error("sticky");
|
||||
});
|
||||
const loader = createLazyPromiseLoader(load, { cacheRejections: true });
|
||||
|
||||
await expect(loader.load()).rejects.toThrow("sticky");
|
||||
await expect(loader.load()).rejects.toThrow("sticky");
|
||||
expect(load).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("clears cached values", async () => {
|
||||
let calls = 0;
|
||||
const loader = createLazyPromiseLoader(() => `loaded-${++calls}`);
|
||||
|
||||
await expect(loader.load()).resolves.toBe("loaded-1");
|
||||
loader.clear();
|
||||
await expect(loader.load()).resolves.toBe("loaded-2");
|
||||
});
|
||||
});
|
||||
|
||||
describe("createLazyImportLoader", () => {
|
||||
it("wraps import-shaped loaders", async () => {
|
||||
const loader = createLazyImportLoader(async () => ({ value: "module" }));
|
||||
|
||||
await expect(loader.load()).resolves.toEqual({ value: "module" });
|
||||
});
|
||||
});
|
||||
44
src/shared/lazy-promise.ts
Normal file
44
src/shared/lazy-promise.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
export type LazyPromiseLoader<T> = {
|
||||
load(): Promise<T>;
|
||||
clear(): void;
|
||||
};
|
||||
|
||||
export type LazyPromiseLoaderOptions = {
|
||||
cacheRejections?: boolean;
|
||||
};
|
||||
|
||||
export function createLazyPromiseLoader<T>(
|
||||
load: () => T | Promise<T>,
|
||||
options: LazyPromiseLoaderOptions = {},
|
||||
): LazyPromiseLoader<T> {
|
||||
let promise: Promise<T> | undefined;
|
||||
|
||||
const createPromise = (): Promise<T> => {
|
||||
const loaded = Promise.resolve().then(load);
|
||||
if (options.cacheRejections !== true) {
|
||||
void loaded.catch(() => {
|
||||
if (promise === loaded) {
|
||||
promise = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
return loaded;
|
||||
};
|
||||
|
||||
return {
|
||||
async load(): Promise<T> {
|
||||
promise ??= createPromise();
|
||||
return await promise;
|
||||
},
|
||||
clear(): void {
|
||||
promise = undefined;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createLazyImportLoader<T>(
|
||||
load: () => Promise<T>,
|
||||
options?: LazyPromiseLoaderOptions,
|
||||
): LazyPromiseLoader<T> {
|
||||
return createLazyPromiseLoader(load, options);
|
||||
}
|
||||
Reference in New Issue
Block a user