通过RxJS运算符限制可观察的网络请求

时间:2018-09-27 03:32:06

标签: angular typescript rxjs

我有一些数据通过网络请求进入Angular应用程序的组件。这是observable,看起来像这样:

    this.streamFiltersService.getByFilters(
        this.page - 1, this.pagesize, this.currentStage, this.language = filters['language'], this.location = filters['location'],
        this.zipcode = filters['zip'], this.firstName = filters['firstName'], this.lastName = filters['lastName'],
        this.branch = filters['branch'], fn);

这里有一个名为fn的回调,它看起来像这样:

    let fn = resRecordsData => {
        this.records = resRecordsData;
    };

我遇到的问题是,随着大量触发过滤器进入组件,我最终遇到了多个网络请求-对于用户来说,屏幕数据会改变多次。此外,由于这是异步的,因此有时最初的请求之一会最后返回,并且过滤器不会应用到用户看到的屏幕中。

完整功能如下:

public onFilterReceived(values)
{
    let filters = {};

    if (values) {
        filters = values;
    }

    this.route.params.subscribe(
        (params: any) => {
            this.page = params['page'];
        }
    );

    console.log('onFilterReceived() firing...');

    let fn = resRecordsData => {
        this.records = resRecordsData;
    };

    this.streamFiltersService.getByFilters(
        this.page - 1, this.pagesize, this.currentStage, this.language = filters['language'], this.location = filters['location'],
        this.zipcode = filters['zip'], this.firstName = filters['firstName'], this.lastName = filters['lastName'],
        this.branch = filters['branch'], fn);
}

作为后续,当我在console.log内的回调上放置onFilterReceived()时,如下所示:

let fn = async resRecordsData => {
    console.log('records: ', resRecordsData);
    this.records = await resRecordsData;
};

打印到控制台的内容是这样的:

records:  {ok: true, status: 200, statusText: "OK", data: Array(1), count: 1}
records:  {ok: true, status: 200, statusText: "OK", data: Array(12), count: 115}
records:  {ok: true, status: 200, statusText: "OK", data: Array(1), count: 1}
records:  {ok: true, status: 200, statusText: "OK", data: Array(1), count: 1}
records:  {ok: true, status: 200, statusText: "OK", data: Array(1), count: 1}
records:  {ok: true, status: 200, statusText: "OK", data: Array(12), count: 115}
records:  {ok: true, status: 200, statusText: "OK", data: Array(12), count: 115}
records:  {ok: true, status: 200, statusText: "OK", data: Array(12), count: 115}
records:  {ok: true, status: 200, statusText: "OK", data: Array(12), count: 115}
records:  {ok: true, status: 200, statusText: "OK", data: Array(12), count: 115}

请注意,由于应用了过滤器,因此正确的值为Array(1)。如您所见,由于这些调用是异步的,因此它们会无序返回。我理想的情况是一个电话,结果如下:

records:  {ok: true, status: 200, statusText: "OK", data: Array(1), count: 1}

基于以下建议,我尝试将建议的运算符链接在onFilterReceived()的回调中,如下所示:

public async onFilterReceived(values)
{
    let filters = {};

    if (values) {
        filters = values;
    }

    this.route.params.subscribe(
        (params: any) => {
            this.page = params['page'];
        }
    );

    console.log('onFilterReceived() firing...');

    let fn = async resRecordsData => {
        await resRecordsData
        .distinctUntilChanged()
        .debounceTime(1000)
        .switchMap( resRecordsData => {
            this.records = resRecordsData;
        });
        console.log('records: ', this.records);
    };

    this.streamFiltersService.getByFilters(
        this.page - 1, this.pagesize, this.currentStage, this.language = filters['language'], this.location = filters['location'],
        this.zipcode = filters['zip'], this.firstName = filters['firstName'], this.lastName = filters['lastName'],
        this.branch = filters['branch'], fn);
}

...但是我最终遇到一个错误:

  

error_handler.js:60错误:未被捕获(承诺):TypeError:   resRecordsData.distinctUntilChanged不是函数

1 个答案:

答案 0 :(得分:2)

因此,如果我正确理解您的问题:

  • 您通过异步回调维护全局状态(this.records
  • 已触发http请求,但无序返回。

这与经常重复的autocomplete example with rxjs类似。

您将需要的代码如下:

$filterChanges
  .distinctUntilchanged()
  .debounceTime(1000)
  .switchMap(filters => getByFilters(.....))
  .subscribe(records => {
    // modify global state with the records
  });

因此,您首先要获取过滤器及其更改的流-例如{ page, pagesize, currentStage, language }。您可能必须在过滤器对象的compareFunc上添加distinctUntilChanged,因为它是非原始的。

下面是一个粗略的非角度实现示例,可以帮助您:

/* make our html filter elements observable */
var carTypeFilter = document.getElementById('cars');
var hornsCheckbox = document.getElementById('horns');

var $carsFilter = Rx.Observable.fromEvent(carTypeFilter, 'change')
  .map(evt => ({ filter: 'car', value: evt.target.value }));
var $hornsFilter = Rx.Observable.fromEvent(hornsCheckbox, 'change')
  .map(evt => ({ filter: 'horns', value: evt.target.checked }));

/* we want to have one value containing the current logical grouping of filters */
var $filtersCombined = Rx.Observable.merge($carsFilter,$hornsFilter)
  .scan((allCurrentFilters, currFilterItem) => {
    allCurrentFilters[currFilterItem.filter] = currFilterItem.value;
    return allCurrentFilters;
  }, {});


var $debouncedFilterChanges = $filtersCombined
  /* for distinctUntilChanged to work
  you would need to have a compareFunction to evaluate
  if all the key:value pairs of the filters are the same
  not going to do that in this example; it will only filter out 
  filter sets after franctic clicking but only result in an additional
  request being done to the backend */
  //  .distinctUntilChanged()
  /* for every distinct filterSetChange debounce it so 
  we know that the user has stopped fiddling with the inputs */
  .debounceTime(500);

var $filteredServerResults = $debouncedFilterChanges
  .switchMap(filters => getFilteredData(filters.car, filters.horns));

$filteredServerResults.subscribe(data => {
  document.getElementById('results').innerText = JSON.stringify(data);
});


/*
mock function which simulates doing async call to server with slow respons
*/
function getFilteredData(car, horns){
  //note that the car or horns can be undefined
  return Rx.Observable.of(`retrieved values for: ${car}-${horns}`)
    .delay(1500); // mimic slow response, user can have already asked newer results
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.12/Rx.js"></script>
</head>
<body>

  <div>
    <select id="cars">
      <option value="">Select one...</option>
      <option value="volvo">Volvo</option>
      <option value="saab">Saab</option>
      <option value="mercedes">Mercedes</option>
      <option value="audi">Audi</option>
    </select>
    <label for="cars">Car</label>
  </div>
  <div>
    <input type="checkbox" id="horns" name="feature"
           value="horns" />
    <label for="horns">HasHorns</label>
  </div>
  <h1>results...</h1>
  <pre id="results"></pre>
</body>
</html>