阻止根据先前发出的值执行

时间:2019-06-25 11:17:51

标签: rxjs

如果我的预先输入得到的搜索结果为空,则应避免随后进行的任何向下排列的搜索查询。例如。如果搜索“ red ”为空,则搜索“ redcar ”没有任何意义。

我尝试使用pairwise()和scan()运算符。代码段:

import { tap, switchMap, filter, pairwise, scan, map } from 'rxjs/operators';

this.searchForm.get('search').valueChanges
  .pipe(
    switchMap( queryString => this.backend.search(queryString))
  )
  .subscribe()

更新 给定一个简化的方案:在后端中只有术语“ apple ”。用户正在键入搜索字符串(该请求不会被switchMap()中止):

  1. 'a'------->后端调用返回'apple'
  2. 'ap'------>后端调用返回'apple'
  3. 'app'----->后端调用返回'apple'
  4. 'appl'---->后端调用返回'apple'
  5. “苹果” --->后端调用返回“苹果”
  6. 'apple p'----->后端调用返回EMPTY
  7. 'apple pi'---->后端调用返回EMPTY
  8. “苹果派” --->后端调用返回EMPTY

不需要后端调用7.和8.,因为6.已经返回了EMPTY。因此,任何后续调用都可以省略。我认为需要一些记忆。

我想防止不必要的后端调用(http)。有什么办法可以在rxjs中实现?

3 个答案:

答案 0 :(得分:2)

您可以使用filter运算符:

this.searchForm.get('search').valueChanges.pipe(
  filter(query => query)
  switchMap(query => this.backend.search(queryString))
)

您可以在这里尝试这种机制:RxJS-Editor

代码共享无效,因此您可以在此处获取代码:

const { of } = Rx;
const { filter } = RxOperators;

of('foo1', 'foo2', undefined, undefined, 'foo3').pipe(
  filter(value => value)  
)

答案 1 :(得分:1)

您想保留所有失败的搜索并检查是否在调用HTTP时当前搜索是否也会失败的声音。我想不出任何一种优雅的方式将它放在一个流中,但是有两个流:

_failedStreams = new Subject();
failedStreams$ = _failedStreams.asObservable().pipe(
  scan((acc, curr) => [...acc, curr], []),
  startWith('')
);

this.searchForm.get('search').valueChanges
  .pipe(
    withLatestFrom(failedStreams$),
    switchMap([queryString, failedQueries] => {
      return iif(() => failedQueries.find(failed => failed.startsWith(queryString)) ?
        of('Not found') :
        callBackend(queryString);
      )
    }
  )
  .subscribe()

callBackend(queryString) {
  this.backend.search(queryString)).pipe(
    .catchError(err => if(error.status===404) {
      this._failedStreams.next(queryString);
      // do something with error stream, for ex:
      throwError(error.status)
    }
  )
}

代码未经测试,但您知道了

答案 2 :(得分:1)

这是一个有趣的用例,是极少数mergeScan有用的情况之一。

基本上,您想记住上一个搜索词和上一个远程呼叫的结果,并根据它们的组合来决定是进行另一个远程呼叫还是只返回EMPTY

import { of, EMPTY, Subject, forkJoin } from 'rxjs'; 
import { mergeScan, tap, filter, map } from 'rxjs/operators';

const source$ = new Subject();
// Returns ['apple'] only when the entire search string is contained inside the word "apple".
// 'apple'.indexOf('app') returns 0
// 'apple'.indexOf('apple ap') returns -1
const makeRemoteCall = (str: string) =>
  of('apple'.indexOf(str) === 0 ? ['apple'] : []).pipe(
    tap(results => console.log(`remote returns`, results)),
  );

source$
  .pipe(
    tap(value => console.log(`searching "${value}""`)),
    mergeScan(([acc, previousValue], value: string) => {
      // console.log(acc, previousValue, value);
      return (acc === null || acc.length > 0 || previousValue.length > value.length)
        ? forkJoin([makeRemoteCall(value), of(value)]) // Make remote call and remember the previous search term
        : EMPTY;
    }, [null, '']),
    map(acc => acc[0]), // Get only the array of responses without the previous search term
    filter(results => results.length > 0), // Ignore responses that didn't find any results
  )
  .subscribe(results => console.log('results', results));

source$.next('a');
source$.next('ap');
source$.next('app');
source$.next('appl');
source$.next('apple');
source$.next('apple ');
source$.next('apple p');
source$.next('apple pi');
source$.next('apple pie');

setTimeout(() => source$.next('app'), 3000);
setTimeout(() => source$.next('appl'), 4000);

实时演示:https://stackblitz.com/edit/rxjs-do457

请注意,在搜索"apple "之后,不再有远程呼叫。同样,在3秒钟后,当您尝试搜索其他术语“ app”时,它的确会再次拨打远程电话。