【问题标题】:Memoize for Observables in Typescript用于 Typescript 中的 Observables 的备忘录
【发布时间】:2017-08-19 23:38:20
【问题描述】:

我正在寻找实现优化的最佳方法,该方法采用多个参数并返回 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();
        });
        });
    }
}
}

任何帮助表示赞赏!

【问题讨论】:

  • 该服务应该可能实现缓存存储而不是基本的记忆。结果应该永不过期吗?应该存储多少个请求?如果错误响应被 momoized 会发生什么?
  • 是的 - 我更喜欢缓存存储而不是基本的记忆,最好是一个包含过期时间、清除缓存的方法等所有内容的包......就数量而言,可能有数百个。跨度>
  • 我会建议像github.com/jmdobry/CacheFactory 这样的东西。至于缓存键,可能是JSON.stringify([param1, param2])
  • 谢谢 - 这简化了它 - 你知道任何支持 Observables 的缓存库吗?
  • 您实际上并不需要支持它们。它只是一个存储,你从中挑选对象并使用 Observable.of 返回它们。

标签: angular typescript rxjs observable memoization


【解决方案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 完全支持)

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      您可以使用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))
      }
      

      【讨论】:

        【解决方案4】:

        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> {
                // ...
            }
        }
        

        【讨论】:

        • 当返回的类型是 Observable 时,这似乎不起作用。我已经围绕它编写了一些测试,但它不起作用。
        • 也许将昂贵的操作(即数据库检索)分离成可以记忆的辅助函数会更好?
        猜你喜欢
        • 2010-10-27
        • 2023-01-17
        • 2013-12-01
        • 2011-01-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-18
        相关资源
        最近更新 更多