RxJS间隔,AJAX请求并减少

时间:2019-03-21 18:30:32

标签: angular rxjs

我试图弄清楚如何在映射数据并使用RxJS运算符减少数据的同时每五秒钟发送一次AJAX请求。

因此,有一个简单的Angular服务,它为目前在太空中的宇航员请求外部API:http://api.open-notify.org/astros.json

在服务中,我尝试先设置interval(5000),然后使用RxJS映射运算符将传入的号码映射到GET请求。

任务是这样的:我需要首先找到宇航员,然后找到那些正在乘坐国际空间站飞行的人,然后再将那些我可以渲染到视图中的宇航员。数据必须每5秒更新一次;因此我需要每5秒重新发送一次相同的HTTP请求,这可以通过setInterval()完成。这不是解决问题的最佳方法。

代码如下:

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError, interval } from 'rxjs';
import { catchError, filter, concatMap, flatMap, map, reduce } from 'rxjs/operators';
import { Astronaut } from '../models/astronaut.model';

const astronautsUri = 'http://api.open-notify.org/astros.json';

@Injectable({
  providedIn: 'root'
})
export class IssLocatorService {
  constructor(private http: HttpClient) {}

  getAstronauts(): Observable<Astronaut[]> {
    return interval(5000).pipe(
      concatMap(num => this.http.get(astronautsUri)),
      flatMap((newAstronauts: any) => newAstronauts.people),
      filter((astronaut: Astronaut) => astronaut.craft === 'ISS'),
      map((astronaut: Astronaut) => [astronaut]),
      reduce((prev, next) => [...prev, ...next]),
      catchError(this.handleError)
    );
  }
}

代码不起作用,a。 尽管流程确实到达了reduce()运算符,但该运算符不会返回组件中的subscribe()方法。

但是,在我看来,该解决方案应该可以正常工作。这是我认为的工作方式:

  1. 5秒后,将产生第一个数字0,并且外部Observable映射到内部Observable-AJAX请求。 concatMap()等到内部Observable完成,然后才获得第二个数字-1。
  2. 内部Observable是一个返回JSON的HTTP请求。我只需要看起来与此类似的物体上的人:{success:true, people: [...]}),然后使用flatMap()将该物体转换为宇航员数组people。由于people的工作原理,flatMap()中的每个对象随后变为可观察对象。
  3. 我过滤了每个宇航员对象。
  4. 我将每个宇航员对象映射到一个要简化的数组。
  5. 我减少了宇航员的人数。换句话说,astronauts.people多亏了reduce()
  6. (这是问题所在),根据规范,reduce()应该返回到subscribe,因为内部Observable已完成。 但不是reduce()等到interval()产生下一个数字并将其映射到内部Observable时,再次返回people并将其推送到相同的数组。并持续不断。

如果我将reduce()替换为scan,则阵列或宇航员返回到subscribe方法。但是,由于宇航员反复将物体推入其中,该阵列不断变大。

以下方法可以正常工作:

return this.http.get(astronautsUri).pipe(
  flatMap((newAstronauts: any) => newAstronauts.people),
  filter((astronaut: Astronaut) => astronaut.craft === 'ISS'),
  map((astronaut: Astronaut) => [astronaut]),
  reduce((prev, next) => [...prev, ...next]),
  catchError(this.handleError)
);

但是在这种情况下,我必须在呈现宇航员的组件类中使用setInterval()手动设置间隔,并且必须调用getAstronauts()方法。因此,基本上ngOnInit中有两次方法调用。

仅使用RxJS运算符怎么能达到预期的效果?我想设置一个间隔,映射并过滤并减少对象的数组,然后接收它们。

我对RxJS映射如何工作的理解确实很糟糕,但是我尝试了(为了尝试)所有这些方法-switchMap()concatMap()exhaustMap(),{{1} }-将flatMap()中的数字映射到AJAX请求。仍然不起作用。

2 个答案:

答案 0 :(得分:0)

我认为您要实现的目标是

getAstronauts(): Observable<Astronaut[]> {
    return interval(5000).pipe(
      concatMap(num => 
        this.http.get(astronautsUri).pipe(
          flatMap((newAstronauts: any) => newAstronauts.people),
          filter((astronaut: Astronaut) => astronaut.craft === 'ISS'),
          reduce((prev, astronaut) => [...prev, astronaut], []),
        )
      ),
      catchError(this.handleError)
    );
  }

reduce方法的问题是它在发出任何值之前正在等待可观察到的源完成。在我的代码中,源是一个请求的元素,有问题的示例中,源是所有请求的所有元素,永远不会结束

答案 1 :(得分:0)

更改reduce进行扫描,scan与reduce进行相同的操作,但是在每次迭代时发出,reduce仅在整个流完成后才发出。

const { from } = rxjs;
const { scan } = rxjs.operators;

const obs$ = from([1,2,3,4,5]);

obs$.pipe(scan((total, item) => total + item)).subscribe(val => { console.log(val); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>

const { from } = rxjs;
const { reduce } = rxjs.operators;

const obs$ = from([1,2,3,4,5]);

obs$.pipe(reduce((total, item) => total + item)).subscribe(val => { console.log(val); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>