Skip to content

Commit

Permalink
feat: emit disposed instance id instead of token
Browse files Browse the repository at this point in the history
  • Loading branch information
bytemain committed Oct 13, 2023
1 parent a5f3c99 commit 76d7dbd
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export function Autowired(token?: Token, opts?: InstanceOpts): PropertyDecorator
}

this[INSTANCE_KEY] = injector.get(realToken, opts);
injector.onceTokenDisposed(realToken, () => {
injector.onceInstanceDisposed(this[INSTANCE_KEY], () => {
this[INSTANCE_KEY] = undefined;
});
}
Expand Down
15 changes: 9 additions & 6 deletions src/helper/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export class EventEmitter<T> {
this._listeners.set(event, []);
}
this._listeners.get(event)!.push(listener);

Check warning on line 8 in src/helper/event.ts

View workflow job for this annotation

GitHub Actions / ci

Forbidden non-null assertion

return () => this.off(event, listener);
}

off(event: T, listener: Function) {
Expand All @@ -20,18 +22,19 @@ export class EventEmitter<T> {
}

once(event: T, listener: Function) {
const onceListener = (...args: any[]) => {
listener(...args);
this.off(event, onceListener);
};
this.on(event, onceListener);
const remove: () => void = this.on(event, (...args: any[]) => {
remove();
listener.apply(this, args);
});

return remove;
}

emit(event: T, ...args: any[]) {
if (!this._listeners.has(event)) {
return;
}
this._listeners.get(event)!.forEach((listener) => listener(...args));
[...this._listeners.get(event)!].forEach((listener) => listener.apply(this, args));

Check warning on line 37 in src/helper/event.ts

View workflow job for this annotation

GitHub Actions / ci

Forbidden non-null assertion
}

dispose() {
Expand Down
37 changes: 24 additions & 13 deletions src/injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,16 @@ export class Injector {
*
* the cycles check is done when register alias provider
*/
resolveAliasToken<T extends Token>(token: T) {
resolveToken<T extends Token>(token: T): [T, T[]] {
const deps = [token] as T[];
let [aliasCreator] = this.getCreator(token);

while (aliasCreator && isAliasCreator(aliasCreator)) {
token = aliasCreator.useAlias as T;

deps.push(token);
[aliasCreator] = this.getCreator(token);
}

return token;
return [token, deps];
}

get<T extends ConstructorOf<any>>(token: T, args?: ConstructorParameters<T>, opts?: InstanceOpts): TokenResult<T>;
Expand All @@ -121,7 +121,7 @@ export class Injector {
args = undefined;
}

token = this.resolveAliasToken(token);
[token] = this.resolveToken(token);

let creator: InstanceCreator | null = null;
let injector: Injector = this;
Expand Down Expand Up @@ -269,19 +269,26 @@ export class Injector {
return this.hookStore.createOneHook(hook);
}

private disposeEventEmitter = new EventEmitter<Token>();
private instanceDisposedEmitter = new EventEmitter<Token>();

onceTokenDisposed(key: Token, cb: () => void) {
return this.disposeEventEmitter.once(key, cb);
onceInstanceDisposed(instance: any, cb: () => void) {
const instanceId = this.getInstanceId(instance);
if (!instanceId) {
return;
}
return this.instanceDisposedEmitter.once(instanceId, cb);
}

disposeOne(token: Token, key = 'dispose') {
const creator = this.creatorMap.get(token);
[token] = this.resolveToken(token);

const [creator] = this.getCreator(token);
if (!creator || creator.status === CreatorStatus.init) {
return;
}

const instance = creator.instance;
const instanceId = this.getInstanceId(instance);
let maybePromise: Promise<unknown> | undefined;
if (instance && typeof instance[key] === 'function') {
maybePromise = instance[key]();
Expand All @@ -292,15 +299,15 @@ export class Injector {

if (maybePromise && Helper.isPromiseLike(maybePromise)) {
maybePromise = maybePromise.then(() => {
this.disposeEventEmitter.emit(token);
this.instanceDisposedEmitter.emit(instanceId);
});
} else {
this.disposeEventEmitter.emit(token);
this.instanceDisposedEmitter.emit(instanceId);
}
return maybePromise;
}

disposeAll(key = 'dispose') {
disposeAll(key = 'dispose'): Promise<void> {
const creatorMap = this.creatorMap;

const promises: (Promise<unknown> | undefined)[] = [];
Expand All @@ -310,7 +317,7 @@ export class Injector {
}

return Promise.all(promises).then(() => {
this.disposeEventEmitter.dispose();
this.instanceDisposedEmitter.dispose();
});
}

Expand Down Expand Up @@ -505,4 +512,8 @@ export class Injector {

return applyHooks(ret, token, this.hookStore);
}

getInstanceId(instance: any) {
return instance && instance.__id;
}
}
41 changes: 41 additions & 0 deletions test/helper/event.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,45 @@ describe('event emitter', () => {
emitter.emit('test', 'hello');
expect(spy).toBeCalledTimes(2);
});

it('many listeners listen to one event', () => {
const emitter = new EventEmitter<string>();
const spy = jest.fn();
const spy2 = jest.fn();
emitter.on('test', spy);
emitter.on('test', spy2);
emitter.emit('test', 'hello');
expect(spy).toBeCalledWith('hello');
expect(spy2).toBeCalledWith('hello');

emitter.off('test', spy);
emitter.emit('test', 'hello');
expect(spy).toBeCalledTimes(1);
expect(spy2).toBeCalledTimes(2);

emitter.dispose();
});

it('can dispose event listener by using returned function', () => {
const emitter = new EventEmitter<string>();
const spy = jest.fn();
const spy2 = jest.fn();
const spy3 = jest.fn();
const disposeSpy = emitter.on('test', spy);
emitter.on('test', spy2);

const disposeSpy3 = emitter.once('test', spy3);
disposeSpy3();

emitter.emit('test', 'hello');
expect(spy).toBeCalledWith('hello');
expect(spy2).toBeCalledWith('hello');

disposeSpy();
emitter.emit('test', 'hello');
expect(spy).toBeCalledTimes(1);
expect(spy2).toBeCalledTimes(2);
expect(spy3).toBeCalledTimes(0);
emitter.dispose();
});
});
44 changes: 43 additions & 1 deletion test/providers/useAlias.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, Injector } from '../../src';
import { Autowired, Injectable, Injector } from '../../src';
import { aliasCircularError } from '../../src/error';

describe('useAlias is work', () => {
Expand Down Expand Up @@ -112,4 +112,46 @@ describe('useAlias is work', () => {
// So the cycle is C **alias to** B **alias to** C
}).toThrowError(aliasCircularError([tokenC, tokenB], tokenC));
});
it('dispose alias will delete all its token', () => {
const injector = new Injector();
const instantiateSpy = jest.fn();
@Injectable()
class A {
constructor() {
instantiateSpy();
}
}

const tokenA = Symbol('tokenA');
const tokenB = Symbol('tokenB');
@Injectable()
class DisposeCls {
@Autowired(tokenA)
a!: A;
@Autowired(tokenB)
aa!: A;
}

injector.addProviders(
{
token: tokenA,
useClass: A,
},
{
token: tokenB,
useAlias: tokenA,
},
);

const disposeCls = injector.get(DisposeCls);
expect(disposeCls.a).toBeInstanceOf(A);
expect(disposeCls.a).toBe(disposeCls.aa);
expect(instantiateSpy).toBeCalledTimes(1);

injector.disposeOne(tokenB);
expect(instantiateSpy).toBeCalledTimes(1);
expect(disposeCls.a).toBeInstanceOf(A);
expect(disposeCls.a).toBe(disposeCls.aa);
expect(instantiateSpy).toBeCalledTimes(2);
});
});

0 comments on commit 76d7dbd

Please sign in to comment.