Skip to content

Commit ddbfaf8

Browse files
committed
feat: add onDisposeValue
1 parent 19e2aa5 commit ddbfaf8

3 files changed

Lines changed: 92 additions & 1 deletion

File tree

src/readable.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ export class ReadableImpl<TValue = any> implements BatchTask {
143143
*/
144144
private _weakRefSelf_?: WeakRef<ReadableImpl<TValue>>;
145145

146+
/**
147+
* @internal
148+
*/
149+
private _onDisposeValue_?: (oldValue: TValue) => void;
150+
146151
public constructor(
147152
resolveValue: (self: ReadableImpl<TValue>) => TValue,
148153
config?: Config<TValue>,
@@ -152,6 +157,7 @@ export class ReadableImpl<TValue = any> implements BatchTask {
152157
this.equal_ = (config?.equal ?? strictEqual) || undefined;
153158
this.name = config?.name;
154159
this.deps_ = deps;
160+
this._onDisposeValue_ = config?.onDisposeValue;
155161
}
156162

157163
/** @internal */
@@ -192,6 +198,9 @@ export class ReadableImpl<TValue = any> implements BatchTask {
192198
}
193199
this.deps_.clear();
194200
}
201+
if (this._onDisposeValue_ && !strictEqual(this._value_, UNIQUE_VALUE)) {
202+
this._onDisposeValue_(this._value_);
203+
}
195204
}
196205

197206
public get(): TValue {
@@ -212,8 +221,12 @@ export class ReadableImpl<TValue = any> implements BatchTask {
212221
try {
213222
const value = this._resolveValue_(this);
214223
if (!this.equal_?.(value, this._value_)) {
224+
const oldValue = this._value_;
215225
this._value_ = value;
216226
this._version_ = (this._version_ + 1) | 0;
227+
if (this._onDisposeValue_ && !strictEqual(oldValue, UNIQUE_VALUE) && !strictEqual(oldValue, value)) {
228+
this._onDisposeValue_(oldValue);
229+
}
217230
}
218231
} catch (e) {
219232
this._valueMaybeDirty_ = true;

src/typings.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ export interface Config<TValue = any> {
1313
* Name for debugging.
1414
*/
1515
readonly name?: string;
16+
/**
17+
* A callback invoked when a value is needed to be disposed.
18+
*
19+
* A value is considered for disposal when:
20+
* - it is replaced by another value (a new value is set).
21+
* - the Readable is disposed.
22+
*
23+
* @param oldValue The value that is needed to be disposed.
24+
*/
25+
readonly onDisposeValue?: (oldValue: TValue) => void;
1626
}
1727

1828
export type Disposer = () => void;

test/readable.test.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
22

3-
import { compute, writable } from "../src";
3+
import { compute, readable, writable } from "../src";
44

55
describe("readable", () => {
66
describe("unsubscribe", () => {
@@ -105,4 +105,72 @@ describe("readable", () => {
105105
expect(JSON.stringify(a)).toBe("null");
106106
});
107107
});
108+
109+
describe("toDisposeValue", () => {
110+
it("should call onDisposeValue when value changes", async () => {
111+
const onDisposeValue = vi.fn();
112+
const obj1 = { a: 1 };
113+
const obj2 = { a: 2 };
114+
const obj3 = { a: 3 };
115+
116+
const [v$, set] = readable(obj1, { onDisposeValue });
117+
expect(v$.value).toBe(obj1);
118+
expect(onDisposeValue).toBeCalledTimes(0);
119+
120+
set(obj1);
121+
expect(v$.value).toBe(obj1);
122+
expect(onDisposeValue).toBeCalledTimes(0);
123+
124+
set(obj2);
125+
expect(v$.value).toBe(obj2);
126+
expect(onDisposeValue).toBeCalledTimes(1);
127+
expect(onDisposeValue).lastCalledWith(obj1);
128+
129+
set(obj2);
130+
expect(v$.value).toBe(obj2);
131+
expect(onDisposeValue).toBeCalledTimes(1);
132+
133+
set(obj3);
134+
expect(v$.value).toBe(obj3);
135+
expect(onDisposeValue).toBeCalledTimes(2);
136+
expect(onDisposeValue).lastCalledWith(obj2);
137+
138+
v$.dispose();
139+
expect(onDisposeValue).toBeCalledTimes(3);
140+
expect(onDisposeValue).lastCalledWith(obj3);
141+
});
142+
143+
it("should call onDisposeValue when value changes (equal: false)", async () => {
144+
const onDisposeValue = vi.fn();
145+
const obj1 = { a: 1 };
146+
const obj2 = { a: 2 };
147+
const obj3 = { a: 3 };
148+
149+
const [v$, set] = readable(obj1, { onDisposeValue, equal: false });
150+
expect(v$.value).toBe(obj1);
151+
expect(onDisposeValue).toBeCalledTimes(0);
152+
153+
set(obj1);
154+
expect(v$.value).toBe(obj1);
155+
expect(onDisposeValue).toBeCalledTimes(0);
156+
157+
set(obj2);
158+
expect(v$.value).toBe(obj2);
159+
expect(onDisposeValue).toBeCalledTimes(1);
160+
expect(onDisposeValue).lastCalledWith(obj1);
161+
162+
set(obj2);
163+
expect(v$.value).toBe(obj2);
164+
expect(onDisposeValue).toBeCalledTimes(1);
165+
166+
set(obj3);
167+
expect(v$.value).toBe(obj3);
168+
expect(onDisposeValue).toBeCalledTimes(2);
169+
expect(onDisposeValue).lastCalledWith(obj2);
170+
171+
v$.dispose();
172+
expect(onDisposeValue).toBeCalledTimes(3);
173+
expect(onDisposeValue).lastCalledWith(obj3);
174+
});
175+
});
108176
});

0 commit comments

Comments
 (0)