Observable将多个函数调用组合成单个Observable

时间:2018-06-06 13:58:43

标签: javascript angular typescript rxjs observable

我有一个基于参数执行http请求的函数。我想添加某种" debounce"功能。因此,如果在设定的时间窗口中多次调用该函数,我想将参数组合成一个请求,而不是发出多个请求。

我想用Observables和Angular来实现这个目标。这听起来并不复杂,但是我无法让它运转起来,也许我错过了一些东西。

现在让我们在单个请求中跳过组合,因为这可以通过聚合去抖或Oberservable.buffer来完成。我在组合单个Observable时遇到了麻烦。

这是我迄今为止所尝试的内容。

我尝试使用主题,因为这似乎是这种情况的正确对象(https://stackblitz.com/edit/angular-hcn41v?file=src%2Fapp%2Fapp.component.ts)。

constructor(private http: HttpClient) {
  this.makeRequest('1').subscribe(x => console.log(x))
  this.makeRequest('2').subscribe(console.log)
  setTimeout(() => {
    this.makeRequest('3').subscribe(console.log)
  }, 1000)
}

private makeRequest(id: string) {
  this.observable = this.observable.pipe(
    merge(Observable.of(id).pipe(delay(1)))
  )
  return this.aggregateDebounce(this.observable)
}

private getUrl(value) {
  console.log('getUrl Call', value);
  return 'https://jsonplaceholder.typicode.com/posts/1';
}

private aggregateDebounce(ob$) {
  const shared$ = ob$.publishReplay(1).refCount()
  return shared$.buffer(shared$.debounceTime(75))
}

我希望有一个' getUrl Call'记录每个函数调用和一个结果日志。但是,如果我向this.makeRequest()添加超过1次调用,我只会得到结果,结果也很奇怪。始终返回所有先前的值。我想我并不完全理解在这种情况下主题是如何运作的。

另一种方法(取自RXJS: Aggregated debounce)是为了创造某种聚合去抖(https://stackblitz.com/edit/angular-mx232d?file=src/app/app.component.ts

constructor(private http: HttpClient) {
  this.makeRequest('1').subscribe(x => console.log(x))
  this.makeRequest('2').subscribe(console.log)
  setTimeout(() => {
    this.makeRequest('3').subscribe(console.log)
  }, 1000)
}

private makeRequest(id: string) {
  this.observable = this.observable.pipe(
    merge(Observable.of(id).pipe(delay(1)))
  )
  return this.aggregateDebounce(this.observable)
}

private getUrl(value) {
  console.log('getUrl Call', value);
  return 'https://jsonplaceholder.typicode.com/posts/1';
}

private aggregateDebounce(ob$) {
  const shared$ = ob$.publishReplay(1).refCount()
  return shared$.buffer(shared$.debounceTime(75))
}

在这种情况下,我遇到的问题是我也获得了以前的所有值。

理论上(至少对我而言)两种变体听起来都是合理的,但似乎我错过了一些东西。任何对正确方向的眨眼都非常感谢。

修改:

根据要求,我添加了最终的实际目标。

想象一下从API请求信息的服务。在50-75ms内,您可以使用特定ID来呼叫服务。我想将这些ID组合到一个请求而不是3。如果100ms之后再次调用该服务,则会发出新的请求

2 个答案:

答案 0 :(得分:1)

this.makeRequest(1).subscribe();

private makeRequest(number: number) {
  this.values.next(number);
  return this.idObservable.pipe(

您在订阅前发出该值 - >价值会丢失。

private values: Subject = new Subject();
private idObservable = this.values.pipe(

private makeRequest(number: number) {
  this.values.next(number);
  return this.idObservable.pipe(    

每次调用都会根据主题创建一个新的observable。每当您发出一个值时,所有订阅者都会收到该值。

可能的解决方案看起来像这样(我在这里使用新的rxjs语法):

subject: Subject<String> = null;
observable = null;
window = 100;

constructor() {
  this.subject = null;
  this.window = 100;

  this.makeRequest('1').subscribe(console.log)
  this.makeRequest('2').subscribe(console.log)
  setTimeout(() => {
    this.makeRequest('3').subscribe(console.log)
  }, 1000)
}

private makeRequest(id: string) {
  if (!this.subject) {
    this.subject = new ReplaySubject()
    this.observable = this.subject.pipe(
      takeUntil(timer(this.window).pipe(take(1))),
      reduce((url, id, index) => this.combine(url, id), baseUrl),
      flatMap(url => this.request(url)),
      tap(() => this.subject = null),
      share()
    )
  }      

  this.subject.next(id);
  return this.observable;
}  

combine创建网址,request发出实际请求。

答案 1 :(得分:1)

Rxjs非常擅长处理这种情况。您将需要两个不同的主题:

  1. 一个将用于收集和合并所有请求
  2. 第二个将用于订阅结果

发出请求时,该值将被推到第一个主题上,而第二个主题将被返回,从而抽象出组合请求的内部逻辑。

private values: Subject = new Subject();
private results: Subject = new Subject();

private makeRequest(number: number) {
  this.values.next(number);
  return this.results;
}

根据需要,合并请求的管道可以是问题中的bufferdebounceTime或其他逻辑。收到响应后,只需将其推入结果主题:

constructor(private http: HttpClient) {
  this.values
    .pipe(
      buffer(this.values.pipe(debounceTime(1000))),
      switchMap(values => this.getUrl(values)),
      map(response => this.results.next(response)))
    .subscribe();
}

在将响应推送到结果上之前,我曾使用switchMap模拟异步请求。

此处的完整示例:https://angular-8yyvku.stackblitz.io