Saber2pr's Blog

远程kv缓存优化

// 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;
  }
}