如何定期使用RxJ抓取REST API?

时间:2018-07-18 10:49:53

标签: angular typescript rxjs

我正在使用RxJs的IntervalObservable每秒轮询一次REST API以获取新的传感器数据。 REST API会以10秒钟内完成的传感器测量值的“存储桶”作为响应,因此来自API的响应也可能包含HTTP标头“ next”,如果有的话,它指向更新的传感器数据存储桶。 >

我当前的实现(见下文)有两个问题:

  • 传感器数据的获取是从过去的时间戳开始的,这可能意味着获取很多数据桶。如果用户离开使用该服务的Angular组件,它将破坏该服务,但初始数据获取将继续进行。我觉得我应该跟踪updateVisualization()方法中getSensorObservations()返回的Observable,并取消订阅销毁服务时的订阅,但这意味着,每当我开始使用IntervalObservable调用updateVisualization()时,我都会具有“(可观察的)(间隔)中的可观察的”,而我需要同时杀死两者。我认为这不是最好的方法。
  • 最初的updateVisualization()可能需要一些时间来获取所有必要的历史数据,但是无论如何,下一次updateVisualization()调用都是通过IntervalObservable启动的,而无需等待初始调用。

您对这些混合可观察物有什么建议吗?

export class WidgetService {
  private widget: Widget;
  private visualizer: any;
  private updateScheduler: Subscription;
  private timestampOfMostRecentObservation?: number;

  constructor(private sensorGateway: SensorGatewayCommunicationService) { }

  public initializeVisualization() {
    this.visualizer = new TimeSeriesLineChartWithTimeRangeSelector();
    this.visualizer.draw(`widget-${this.widget.id}-visualization`, this.widget.name, this.widget.seriesName);
    // First update of the visualization with sensor data since a timestamp in the past (121 seconds ago here):
    const initialFromTimestamp = Date.now() - 121 * 1000;
    this.updateVisualization(initialFromTimestamp);
    // Next updates of the visualization are scheduled every second:
    this.updateScheduler = IntervalObservable.create(1000)
      .subscribe(() => this.updateVisualization(this.timestampOfMostRecentObservation));
  }

  public destroy() {
    this.updateScheduler.unsubscribe();
    this.visualizer.destroy();
  }

  private updateVisualization(fromTimestamp: number) {
    const urlForNewObservations = this.widget.sensorMeasurementsUrl + `?from=${fromTimestamp.toString()}`;
    this.getSensorObservations(urlForNewObservations)
      .pipe(
        expand(({sensorObservations, nextUrl}) => { // https://blog.angularindepth.com/rxjs-understanding-expand-a5f8b41a3602
          if (sensorObservations && sensorObservations.length > 0 && nextUrl) {
            return this.getSensorObservations(nextUrl);
          } else {
            return empty();
          }
        }),
        concatMap(({sensorObservations}) => sensorObservations),
      )
      .subscribe((sensorObservations: [number, number][]) => {
        const downsampledObservations = this.downsampleSensorObservations(sensorObservations);
        this.visualizer.update(downsampledObservations);
      });
  }

  private getSensorObservations(urlForNewObservations: string): Observable<{
    sensorObservations: object[],
    nextUrl: string | null
  }> {
    return this.sensorGateway.getApiResource(urlForNewObservations).pipe(
      map(response => {
        if ('values' in response.body) {
          return {
            sensorObservations: response.body['values'].map(observation => [
              observation[0],
              observation[1]
            ]),
            nextUrl: this.getNextLinkUrl(response)
          };
        } else {
          return null;
        }
      })
    );
  }

  private getNextLinkUrl(response: HttpResponse<object>): string | null {
    if (response.headers.has('link')) {
      const linkHeader = response.headers.get('link');
      /* Example of a linkHeader:
       *'</sensors/1/properties/1/observations/20180711/12/19?from=1531311594456>; rel="self",
       * </sensors/1/properties/1/observations/20180711/12/18>; rel="prev",
       * </sensors/1/properties/1/observations/20180711/12/20>; rel="next"' */
      const links = linkHeader.split(',');
      const nextLink = links.find(link => link.endsWith('; rel="next"'));
      if (nextLink) {
        return nextLink.substring(nextLink.indexOf('<') + 1, nextLink.indexOf('>'));
      } else {
        return null;
      }
    }
  }
}

1 个答案:

答案 0 :(得分:1)

我不是使用一个可观察对象的订阅来触发另一个,而是将问题抛在脑后,创建一个可以满足您需要的可观察对象。

我会在您的初始化方法中提出类似的建议:

let fromTimestamp = Date.now() - 121 * 1000;
// Create a base observable, doesn't really matter what it is
this.subscription = of(true).pipe(
    // Map to the right call fur the current time
    flatMap(() => {
        const urlForNewObservations = this.widget.sensorMeasurementsUrl + `?from=${fromTimestamp.toString()}`;
        return this.getSensorObservations(urlForNewObservations);
    }),

    // Repeat the REST call while the sensor returns a next URL:
    expand(({sensorObservations, nextUrl}) => { // https://blog.angularindepth.com/rxjs-understanding-expand-a5f8b41a3602

      if (sensorObservations && sensorObservations.length > 0 && nextUrl) {
        // Set the fromTimestamp for the next set of observations.
        fromTimestamp = this.parseTimestamp(nextUrl, fromTimestamp);
        return this.getSensorObservations(nextUrl);
      } else {
        return empty();
      }
    }),
    concatMap(({sensorObservations}) => sensorObservations),

    // Keep repeating this
    repeat(),

    // But wait a second between each one
    delay(1000),        

    // Terminate the whole thing when the service is destroyed / stopped.              
    takeWhile(() => !this.destroyed)  
).subscribe((sensorObservations: [number, number][]) => {
    const downsampledObservations = this.downsampleSensorObservations(sensorObservations);
    this.visualizer.update(downsampledObservations);
});

您将需要实现parseTimestamp来从URL或类似名称中解析相关的下一个时间戳。

然后实现ngOnDestroy将此this.destroyed设置为true并执行if (this.subscription) this.subscription.unsubscribe();,这将在服务被销毁时终止订阅-如果要手动控制,请在您的destroy方法中将其手动设置为true /退订