From 0863e0d139f12c444a2a01bb899bc3148c52e7ce Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 27 Jul 2020 18:18:20 +0800 Subject: Refactor SubscriptionHub. --- Timeline/ClientApp/src/app/data/SubscriptionHub.ts | 43 ++++++++++++---------- Timeline/ClientApp/src/app/data/timeline.ts | 15 +++++--- Timeline/ClientApp/src/app/data/user.ts | 14 ++++--- 3 files changed, 42 insertions(+), 30 deletions(-) (limited to 'Timeline/ClientApp/src') diff --git a/Timeline/ClientApp/src/app/data/SubscriptionHub.ts b/Timeline/ClientApp/src/app/data/SubscriptionHub.ts index 92a54bc7..f09cd9f8 100644 --- a/Timeline/ClientApp/src/app/data/SubscriptionHub.ts +++ b/Timeline/ClientApp/src/app/data/SubscriptionHub.ts @@ -8,7 +8,6 @@ // There might be some bugs, especially memory leaks and in asynchronization codes. import * as rxjs from 'rxjs'; -import { filter } from 'rxjs/operators'; export type Subscriber = (data: TData) => void; @@ -26,25 +25,25 @@ class SubscriptionToken { class SubscriptionLine { private _lastDataPromise: Promise; - private _dataSubject = new rxjs.BehaviorSubject(undefined); - private _data$: rxjs.Observable = this._dataSubject.pipe( - filter((d) => d !== undefined) - ) as rxjs.Observable; + private _dataSubject: rxjs.BehaviorSubject; private _refCount = 0; constructor( - _creator: () => Promise, + defaultValueProvider: () => TData, + setup: ((old: TData) => Promise) | undefined, private _destroyer: ((data: TData) => void) | undefined, private _onZeroRef: (self: SubscriptionLine) => void ) { - this._lastDataPromise = _creator().then((data) => { - this._dataSubject.next(data); - return data; - }); + const initValue = defaultValueProvider(); + this._lastDataPromise = Promise.resolve(initValue); + this._dataSubject = new rxjs.BehaviorSubject(initValue); + if (setup != null) { + this.next(setup); + } } subscribe(subscriber: Subscriber): SubscriptionToken { - const subscription = this._data$.subscribe(subscriber); + const subscription = this._dataSubject.subscribe(subscriber); this._refCount += 1; return new SubscriptionToken(subscription); } @@ -53,11 +52,12 @@ class SubscriptionLine { token._subscription.unsubscribe(); this._refCount -= 1; if (this._refCount === 0) { - void this._lastDataPromise.then((data) => { - if (this._destroyer != null && data !== undefined) { - this._destroyer(data); - } - }); + const { _destroyer: destroyer } = this; + if (destroyer != null) { + void this._lastDataPromise.then((data) => { + destroyer(data); + }); + } this._onZeroRef(this); } } @@ -67,7 +67,7 @@ class SubscriptionLine { .then((old) => updator(old)) .then((data) => { const last = this._dataSubject.value; - if (this._destroyer != null && last !== undefined) { + if (this._destroyer != null) { this._destroyer(last); } this._dataSubject.next(data); @@ -82,9 +82,11 @@ export interface ISubscriptionHub { export class SubscriptionHub implements ISubscriptionHub { + // If setup is set, update is called with setup immediately after setting default value. constructor( public keyToString: (key: TKey) => string, - public creator: (key: TKey) => Promise, + public defaultValueProvider: (key: TKey) => TData, + public setup?: (key: TKey) => Promise, public destroyer?: (key: TKey, data: TData) => void ) {} @@ -95,9 +97,10 @@ export class SubscriptionHub const line = (() => { const savedLine = this.subscriptionLineMap.get(keyString); if (savedLine == null) { - const { destroyer } = this; + const { setup, destroyer } = this; const newLine = new SubscriptionLine( - () => this.creator(key), + () => this.defaultValueProvider(key), + setup != null ? () => setup(key) : undefined, destroyer != null ? (data) => { destroyer(key, data); diff --git a/Timeline/ClientApp/src/app/data/timeline.ts b/Timeline/ClientApp/src/app/data/timeline.ts index f2c3fdda..7ade8f56 100644 --- a/Timeline/ClientApp/src/app/data/timeline.ts +++ b/Timeline/ClientApp/src/app/data/timeline.ts @@ -131,6 +131,7 @@ export class TimelineService { TimelinePostInfo[] >( (key) => key, + () => [], async (key) => { return ( await getHttpTimelineClient().listPost( @@ -148,8 +149,12 @@ export class TimelineService { return this._postListSubscriptionHub; } - private _postDataSubscriptionHub = new SubscriptionHub( + private _postDataSubscriptionHub = new SubscriptionHub< + PostKey, + BlobWithUrl | null + >( (key) => `${key.timelineName}/${key.postId}`, + () => null, async (key) => { const blob = ( await getHttpTimelineClient().getPostData( @@ -165,11 +170,11 @@ export class TimelineService { }; }, (_key, data) => { - URL.revokeObjectURL(data.url); + if (data != null) URL.revokeObjectURL(data.url); } ); - get postDataHub(): ISubscriptionHub { + get postDataHub(): ISubscriptionHub { return this._postDataSubscriptionHub; } @@ -275,8 +280,8 @@ export function usePostDataUrl( timelineName, postId, }, - ({ url }) => { - setUrl(url); + (data) => { + setUrl(data?.url); } ); return () => { diff --git a/Timeline/ClientApp/src/app/data/user.ts b/Timeline/ClientApp/src/app/data/user.ts index 1be5cd3e..dec9929f 100644 --- a/Timeline/ClientApp/src/app/data/user.ts +++ b/Timeline/ClientApp/src/app/data/user.ts @@ -233,8 +233,12 @@ export class UserNotExistError extends Error {} export type AvatarInfo = BlobWithUrl; export class UserInfoService { - private _avatarSubscriptionHub = new SubscriptionHub( + private _avatarSubscriptionHub = new SubscriptionHub< + string, + AvatarInfo | null + >( (key) => key, + () => null, async (key) => { const blob = (await getHttpUserClient().getAvatar(key)).data; const url = URL.createObjectURL(blob); @@ -244,7 +248,7 @@ export class UserInfoService { }; }, (_key, data) => { - URL.revokeObjectURL(data.url); + if (data != null) URL.revokeObjectURL(data.url); } ); @@ -265,7 +269,7 @@ export class UserInfoService { ); } - get avatarHub(): ISubscriptionHub { + get avatarHub(): ISubscriptionHub { return this._avatarSubscriptionHub; } } @@ -284,8 +288,8 @@ export function useAvatarUrl(username?: string): string | undefined { const subscription = userInfoService.avatarHub.subscribe( username, - ({ url }) => { - setAvatarUrl(url); + (info) => { + setAvatarUrl(info?.url); } ); return () => { -- cgit v1.2.3