Skip to content

Commit

Permalink
feat: add disposer list support
Browse files Browse the repository at this point in the history
  • Loading branch information
crimx committed Jun 6, 2022
1 parent 71f964f commit 41f3ce7
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 18 deletions.
18 changes: 13 additions & 5 deletions src/async-side-effect-manager.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { genUID } from "./gen-uid";
import { invoke } from "./utils";

export type AsyncSideEffectDisposer = () => Promise<void> | void;
export type AsyncSideEffectDisposer = () => Promise<any> | any;

export type AsyncSideEffectExecutor = () =>
| Promise<AsyncSideEffectDisposer>
| AsyncSideEffectDisposer;
| Promise<AsyncSideEffectDisposer | AsyncSideEffectDisposer[]>
| AsyncSideEffectDisposer
| AsyncSideEffectDisposer[];

export class AsyncSideEffectManager {
/**
Expand Down Expand Up @@ -41,7 +43,13 @@ export class AsyncSideEffectManager {
}

try {
this.disposers.set(disposerID, await executor());
const disposers = await executor();
this.disposers.set(
disposerID,
Array.isArray(disposers)
? async () => Promise.all(disposers.map(invoke))
: disposers
);
} catch (e) {
console.error(e);
}
Expand All @@ -62,7 +70,7 @@ export class AsyncSideEffectManager {
* @returns disposerID
*/
public addDisposer(
disposer: AsyncSideEffectDisposer,
disposer: AsyncSideEffectDisposer | AsyncSideEffectDisposer[],
disposerID: string = this.genUID()
): string {
return this.add(() => disposer, disposerID);
Expand Down
24 changes: 11 additions & 13 deletions src/side-effect-manager.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
import { genUID } from "./gen-uid";
import { invoke } from "./utils";

export type SideEffectDisposer = () => void;
export type SideEffectDisposer = () => any;

export class SideEffectManager {
/**
* Add a disposer directly.
* @param disposer a disposer
* @param disposer a disposer or a list of disposers
* @param disposerID Optional id for the disposer
* @returns disposerID
*/
public addDisposer(
disposer: SideEffectDisposer,
disposer: SideEffectDisposer | SideEffectDisposer[],
disposerID: string = this.genUID()
): string {
this.flush(disposerID);
this.disposers.set(disposerID, disposer);
this.disposers.set(
disposerID,
Array.isArray(disposer) ? () => disposer.forEach(invoke) : disposer
);
return disposerID;
}

/**
* Add a side effect.
* @param executor execute side effect
* @param executor executes side effect, returns a disposer or a list of disposers
* @param disposerID Optional id for the disposer
* @returns disposerID
*/
public add(
executor: () => SideEffectDisposer,
executor: () => SideEffectDisposer | SideEffectDisposer[],
disposerID: string = this.genUID()
): string {
return this.addDisposer(executor(), disposerID);
Expand Down Expand Up @@ -147,13 +151,7 @@ export class SideEffectManager {
* Remove and run all of the disposers.
*/
public flushAll(): void {
this.disposers.forEach(disposer => {
try {
disposer();
} catch (e) {
console.error(e);
}
});
this.disposers.forEach(invoke);
this.disposers.clear();
}

Expand Down
7 changes: 7 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function invoke<T>(fn: () => T): T | void {
try {
return fn();
} catch (e) {
console.error(e);
}
}
105 changes: 105 additions & 0 deletions test/async-side-effect-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,39 @@ describe("add", () => {

spy.mockRestore();
});

it("should accept a list of disposes returned from executor", async () => {
const sideEffect = new AsyncSideEffectManager();
const executer = jest.fn();
const disposer1 = jest.fn();
const disposer2 = jest.fn();

sideEffect.add(async () => {
executer("execute1");
return [disposer1, disposer2];
});

await sideEffect.finished;

expect(executer).toBeCalledTimes(1);
expect(executer).lastCalledWith("execute1");
expect(disposer1).toBeCalledTimes(0);
expect(disposer2).toBeCalledTimes(0);
expect(sideEffect.disposers.size).toBe(1);

sideEffect.add(() => {
executer("execute2");
return [disposer1, disposer2];
});

await sideEffect.finished;

expect(executer).toBeCalledTimes(2);
expect(executer).lastCalledWith("execute2");
expect(disposer1).toBeCalledTimes(0);
expect(disposer2).toBeCalledTimes(0);
expect(sideEffect.disposers.size).toBe(2);
});
});

describe("addDisposer", () => {
Expand All @@ -192,6 +225,20 @@ describe("addDisposer", () => {
expect(sideEffect.disposers.size).toBe(1);
});

it("should add a list of disposers", async () => {
const sideEffect = new AsyncSideEffectManager();
const disposers = Array.from({ length: 5 }).map(() => jest.fn());

sideEffect.addDisposer(disposers);

await sideEffect.finished;

disposers.forEach(disposer => {
expect(disposer).toBeCalledTimes(0);
});
expect(sideEffect.disposers.size).toBe(1);
});

it("should add two disposers", async () => {
const sideEffect = new AsyncSideEffectManager();
const disposer1 = jest.fn();
Expand Down Expand Up @@ -276,6 +323,35 @@ describe("remove", () => {
expect(sideEffect.disposers.size).toBe(0);
});

it("should remove a side effect with a list of disposers", async () => {
const sideEffect = new AsyncSideEffectManager();
const executer = jest.fn();
const disposers = Array.from({ length: 5 }).map(() => jest.fn());

const disposerID = sideEffect.add(async () => {
executer("execute");
return disposers;
});

await sideEffect.finished;

expect(executer).toBeCalledTimes(1);
disposers.forEach(disposer => {
expect(disposer).toBeCalledTimes(0);
});
expect(sideEffect.disposers.size).toBe(1);

sideEffect.remove(disposerID);

await sideEffect.finished;

expect(executer).toBeCalledTimes(1);
disposers.forEach(disposer => {
expect(disposer).toBeCalledTimes(0);
});
expect(sideEffect.disposers.size).toBe(0);
});

it("should be able to call remove on a removed disposerID", async () => {
const sideEffect = new AsyncSideEffectManager();
const executer = jest.fn();
Expand Down Expand Up @@ -391,6 +467,35 @@ describe("flush", () => {
expect(sideEffect.disposers.size).toBe(0);
});

it("should flush a side effect with a list of disposers", async () => {
const sideEffect = new AsyncSideEffectManager();
const executer = jest.fn();
const disposers = Array.from({ length: 5 }).map(() => jest.fn());

const disposerID = sideEffect.add(async () => {
executer("execute");
return disposers;
});

await sideEffect.finished;

expect(executer).toBeCalledTimes(1);
disposers.forEach(disposer => {
expect(disposer).toBeCalledTimes(0);
});
expect(sideEffect.disposers.size).toBe(1);

sideEffect.flush(disposerID);

await sideEffect.finished;

expect(executer).toBeCalledTimes(1);
disposers.forEach(disposer => {
expect(disposer).toBeCalledTimes(1);
});
expect(sideEffect.disposers.size).toBe(0);
});

it("should be able to call flush on a flushed disposerID", async () => {
const sideEffect = new AsyncSideEffectManager();
const executer = jest.fn();
Expand Down
91 changes: 91 additions & 0 deletions test/side-effect-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,35 @@ describe("add", () => {
expect(disposer).lastCalledWith("dispose1");
expect(sideEffect.disposers.size).toBe(1);
});

it("should accept a list of disposes returned from executor", () => {
const sideEffect = new SideEffectManager();
const executer = jest.fn();
const disposer1 = jest.fn();
const disposer2 = jest.fn();

sideEffect.add(() => {
executer("execute1");
return [disposer1, disposer2];
});

expect(executer).toBeCalledTimes(1);
expect(executer).lastCalledWith("execute1");
expect(disposer1).toBeCalledTimes(0);
expect(disposer2).toBeCalledTimes(0);
expect(sideEffect.disposers.size).toBe(1);

sideEffect.add(() => {
executer("execute2");
return [disposer1, disposer2];
});

expect(executer).toBeCalledTimes(2);
expect(executer).lastCalledWith("execute2");
expect(disposer1).toBeCalledTimes(0);
expect(disposer2).toBeCalledTimes(0);
expect(sideEffect.disposers.size).toBe(2);
});
});

describe("addDisposer", () => {
Expand All @@ -99,6 +128,18 @@ describe("addDisposer", () => {
expect(sideEffect.disposers.size).toBe(1);
});

it("should add a list of disposers", () => {
const sideEffect = new SideEffectManager();
const disposers = Array.from({ length: 5 }).map(() => jest.fn());

sideEffect.addDisposer(disposers);

disposers.forEach(disposer => {
expect(disposer).toBeCalledTimes(0);
});
expect(sideEffect.disposers.size).toBe(1);
});

it("should add two disposers", () => {
const sideEffect = new SideEffectManager();
const disposer1 = jest.fn();
Expand Down Expand Up @@ -171,6 +212,31 @@ describe("remove", () => {
expect(sideEffect.disposers.size).toBe(0);
});

it("should remove a side effect with a list of disposers", () => {
const sideEffect = new SideEffectManager();
const executer = jest.fn();
const disposers = Array.from({ length: 5 }).map(() => jest.fn());

const disposerID = sideEffect.add(() => {
executer("execute");
return disposers;
});

expect(executer).toBeCalledTimes(1);
disposers.forEach(disposer => {
expect(disposer).toBeCalledTimes(0);
});
expect(sideEffect.disposers.size).toBe(1);

sideEffect.remove(disposerID);

expect(executer).toBeCalledTimes(1);
disposers.forEach(disposer => {
expect(disposer).toBeCalledTimes(0);
});
expect(sideEffect.disposers.size).toBe(0);
});

it("should be able to call remove on a removed disposerID", () => {
const sideEffect = new SideEffectManager();
const executer = jest.fn();
Expand Down Expand Up @@ -276,6 +342,31 @@ describe("flush", () => {
expect(sideEffect.disposers.size).toBe(0);
});

it("should flush a side effect with a list of disposers", () => {
const sideEffect = new SideEffectManager();
const executer = jest.fn();
const disposers = Array.from({ length: 5 }).map(() => jest.fn());

const disposerID = sideEffect.add(() => {
executer("execute");
return disposers;
});

expect(executer).toBeCalledTimes(1);
disposers.forEach(disposer => {
expect(disposer).toBeCalledTimes(0);
});
expect(sideEffect.disposers.size).toBe(1);

sideEffect.flush(disposerID);

expect(executer).toBeCalledTimes(1);
disposers.forEach(disposer => {
expect(disposer).toBeCalledTimes(1);
});
expect(sideEffect.disposers.size).toBe(0);
});

it("should be able to call flush on a flushed disposerID", () => {
const sideEffect = new SideEffectManager();
const executer = jest.fn();
Expand Down

0 comments on commit 41f3ce7

Please sign in to comment.