为打字稿中的Observable记忆

时间:2017-08-19 23:38:20

标签: angular typescript rxjs observable memoization

我正在寻找以非常昂贵的方法实现优化的最佳方法,该方法需要多个参数并返回一个Observable。有一种优雅的方式吗?

我正在寻找的是更漂亮的版本:

class Example {

constructor(
       private databaseService: DatabaseService, 
       private someService: SomeService) 

expensive(param1: string, param2: string) : Observable<string> {
    if (isMemoraized(param1,param2) { 
       return Observable.create(observer=>
         observer.next(memorizedValue(param1, param2));
         observer.complete();
    } else {
        return Observable.create(observer=>{
           Observable.forkJoin([
           this.databaseService.getValue(param1, param2),
           this.someService.fetchDataFromServer(param2)].subscribe( 
          results => {
        let result = results[0] + ' ' + results[1];
        memorizeValue([param1,param2], result);
        observer.next(result);
        observer.complete();
        });
        });
    }
}
}

任何帮助表示赞赏!

4 个答案:

答案 0 :(得分:1)

NPM上有许多便笺程序包。对于TypeScript,我推荐typescript-memoize,它将为您提供一个装饰器,您可以使用它来记忆您的方法。

例如:

import {Memoize} from 'typescript-memoize';

class Example {

    @Memoize((param1: string, param2: string) => {
        return param1 + ';' + param2;
    })
    expensive(param1: string, param2: string) : Observable<string> {
        // ...
    }
}

答案 1 :(得分:1)

如果您不愿意使用任何库并编写自己的代码。我可以将你的代码重构为:

expensive(param1: string, param2: string) : Observable<string> {
    return isMemoraized(param1, param2)
        ? Observable.of(memorizedValue(param1, param2))
        : Observable.forkJoin([
            this.databaseService.getValue(param1, param2),
            this.someService.fetchDataFromServer(param2)
          ])
          .map(results => results[0] +results[1])
          .do( memorizeValue(result);

}

答案 2 :(得分:1)

您可以使用localStorage来保存昂贵操作的结果,并通过两个参数的哈希进行索引。我的下面的解决方案也实现了到期以避免使用陈旧的结果。

/**
 * Gets the key to be used to store result. Each key should be unique 
 * to the parameters supplied,
 * and the same parameters should always yield the same key
 * @return {string}
 */
getKey(param1, param2){
  return `${param1}__${param2}`;
}

/**
 * Stores results in localStorage and sets expiration date into the future
 */
store(param1, param2, result, secsToExpire){
  let toStore = {
    data: result,
    expires: Math.floor(Date.now() / 1000) + secsToExpire
  };
  localStorage.setItem(this.getKey(param1,param2), JSON.stringify(toStore));
}

/**
 * Gets result from storage. If result is stale (expired) or unavailable,
 * returns NULL
 * @return {string|null}
 */
retrieve(param1, param2){
  let result = localStorage.getItem(getKey(param1,param2));
  if(!result==null) result = JSON.parse(result);
  if(result==null || result.expires < Math.floor(Date.now() / 1000)){
    return null;
  }
  return result.data;
}

/**
 * Gets result from localStorage if available. Else, fetch from server
 * and store before returning an Observable that will emit the result
 * @return {Observable<string>}
 */
expensive(param1, param2):Observable<string>{
  let result = this.retrieve(param1,param2);
  if(result) return Observable.of(result);

  // zip will match up outputs into an array
  return Observable.zip(
    this.databaseService.getValue(param1, param2),
    this.someService.fetchDataFromServer(param2)
  ) // take ensures completion after 1 result.
    .take(1).map(e => e[0] + ' ' + e[1])
    // store in localStorage
    .do(res => this.store(param1,param2, res))
}

答案 3 :(得分:1)

您可以创建一个装饰器以在运行时为每个装饰的函数记录结果:

function Memoized() {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const method = descriptor.value; // references the method being decorated
    let cacheMember = propertyKey + "CacheMember";

    // the Observable function
    if (!descriptor.value) {
      throw new Error("use MemoizeDecorator only on services methods");
    }

    descriptor.value = function(...args) {
      if (!target[cacheMember]) {
        let returnedObservable = method.apply(this, args);
        if (!(returnedObservable instanceof Observable)) {
          throw new Error(
            `method decorated with Memoized Decorator must return Observable`
          );
        }

        target[cacheMember] = returnedObservable.pipe(
          publishReplay(),
          refCount()
        );
      }

      return target[cacheMember];
    };
  };
}

用法:

@Memoized()
expensive(param1: string, param2: string) : Observable<string> {
// ...the expensive task
}

警告! 装饰器是js的stage 2 proposal! 不要在未转译代码的情况下使用修饰符(Typescript完全支持它)