// set和push用于和源同步DiffValue
// 调用set时,会调用push函数,同时缓存值会变为脏值
// 再下一次get时会检查是否为脏值,如果是则通过fetch获取并更新变量缓存,如果不为脏值则取变量缓存
// 对push和set函数进行了缓存优化,对于相同的uuid,需要等待已有任务结束
export interface DiffValueOps<T> {
fetch(): Promise<T>;
push(val: T): Promise<any>;
}
export class DiffValue<T> {
private isOld = true;
private value: T = null;
constructor(private defaultValue: T, private ops: DiffValueOps<T>) {
this.isOld = true;
this.value = defaultValue;
}
public async set(val: T) {
this.value = val;
await this.push();
this.isOld = true;
}
public async get() {
if (this.isOld) {
await this.fetch();
this.isOld = false;
}
return this.value || this.defaultValue;
}
public setIsOld() {
this.isOld = true;
}
private async fetch() {
const newVal = await this.ops.fetch();
this.value = newVal;
}
private async push() {
await this.ops.push(this.value);
}
}
export class DiffAsyncValue<T> extends DiffValue<T> {
constructor(private uuid: string, defaultValue: T, ops: DiffValueOps<T>) {
super(defaultValue, {
fetch: () => DiffAsyncValue.getTaskByKey('fetch', uuid, ops.fetch),
push: async val => {
return DiffAsyncValue.getTaskByKey('push', uuid, () => ops.push(val));
},
});
}
public async set(val: T) {
await super.set(val);
this.dispatch(); // call listeners
}
private static listeners: Map<string, VoidFunction[]> = new Map();
subscribe(listener: VoidFunction) {
const listeners = DiffAsyncValue.listeners.get(this.uuid) || [];
const newListeners = listeners.filter(l => l !== listener).concat(listener);
DiffAsyncValue.listeners.set(this.uuid, newListeners);
return () => this.unsubscribe(listener);
}
private unsubscribe(listener: VoidFunction) {
const listeners = DiffAsyncValue.listeners.get(this.uuid) || [];
const newListeners = listeners.filter(l => l !== listener);
DiffAsyncValue.listeners.set(this.uuid, newListeners);
}
dispatch() {
const listeners = DiffAsyncValue.listeners.get(this.uuid) || [];
listeners.forEach(l => l());
}
private static asyncTasks: Record<string, Promise<any>> = {};
// dedup async calls
private static getTaskByKey = (
type: 'fetch' | 'push',
uuid: string,
fetch: () => Promise<any>
) => {
const asyncTasks = this.asyncTasks;
const keyFetch = asyncTasks[uuid];
if (keyFetch) {
if (type === 'push') {
console.warn(`[DiffAsyncValue] ${uuid} 同一时间进行了多次push, 请检查逻辑是否正常!`);
}
return keyFetch;
}
const task = fetch();
asyncTasks[uuid] = task;
task.finally(() => {
asyncTasks[uuid] = null;
});
return asyncTasks[uuid];
};
private static cacheMap: Record<string, DiffAsyncValue<any>> = {};
public static getCache(key: string, defaultValue: any, ops: DiffValueOps<any>) {
const cacheMap = this.cacheMap;
if (!cacheMap[key]) {
cacheMap[key] = new DiffAsyncValue(key, defaultValue, ops);
}
const valueCached = cacheMap[key];
return valueCached;
}
}