多个可观察者一起工作

时间:2017-03-22 16:36:53

标签: angular reactjs react-native rxjs observable

我有一个输入,当用户输入时,它会执行实时搜索。例如,假设他搜索了以下内容:

  

结果将是:

[
  {
    id: "1"
    name: "Ferrari"
  },
  {
    id: "2"
    name: "Porsche"
  }
]

我能够成功完成,方法如下:

class WordComponent {
  word: Subject<string> = new Subject<string>();
  result: any[] = [];

  constructor(private http: Http) {
    this.subscribe();
  }

  subscribe(): void {
    this.word.debounceTime(400)
      .distinctUntilChanged()
      .switchMap((word: string): Observable<any[]> => this.http.get(word))
      .subscribe((result: any[]): any[] => this.result = result);
  }

  search(event: any): void {
    this.word.next(event.target.value);
  }

}

观点:

<input type="text" placeholder="Word" (keyup)="search($event)">

我希望用户能够同时输入多个单词并分别对每个单词进行实时搜索。例如,假设他搜索了以下内容:

  汽车食品太阳

car 的结果是:

[
  {
    id: "1"
    name: "Ferrari"
  },
  {
    id: "2"
    name: "Porsche"
  }
]

食物的结果将是:

[
  {
    id: "3"
    name: "egg"
  },
  {
    id: "4"
    name: "cheese"
  }
]

sun 的结果将是:

[
  {
    id: "5"
    name: "star"
  },
  {
    id: "6"
    name: "sky"
  }
]

并且还合并每个单词的结果,在这种情况下它看起来像这样:

[
  [{
      id: "1"
      name: "Ferrari"
    },
    {
      id: "2"
      name: "Porsche"
    }
  ],
  [{
      id: "3"
      name: "egg"
    },
    {
      id: "4"
      name: "cheese"
    }
  ],
  [{
      id: "5"
      name: "star"
    },
    {
      id: "6"
      name: "sky"
    }
  ]
]

但是,让我们说用户在输入所有单词并执行搜索后,希望更改其中一个。只需要重新搜索已更改的单词,最后结果的合并也必须重做。

我仍然不知道rxjs的所有功能,我不知道实现这一目标的理想方法是什么。如果您想要参考,Display Purposes网站的搜索引擎非常相似。

3 个答案:

答案 0 :(得分:0)

我认为你需要这样的东西:

subscribe(): void {
  this.word.debounceTime(400)
  .distinctUntilChanged()
  .switchMap((words: string): Observable<any[]> => 
    Observable.forkJoin(words.split(' ').map(word => this.http.get(word))) 
  )
  .map(arrayWithArrays => [].concat(arrayWithArrays)
  .subscribe((result: any[]): any[] => this.result = result);
 }

答案 1 :(得分:0)

我已经提出了这个巨大但部分的解决方案:

在这里小提琴:https://jsfiddle.net/mtawrhLs/1/

Rx.Observable.prototype.combineAllCont = function () {
    let lastValues = [];

    return Rx.Observable.create(obs => {
        let i = 0;

        let subscription = this.subscribe(stream => {
            const streamIndex = i;
            subscription.add(stream.subscribe(res => {
                lastValues[streamIndex] = res;
                obs.next(lastValues);
            }));
            i++;
        });

        return subscription;
    });
}

/** STUFF TO DEMO **/
let searchBox = [
    '',
    'car',
    'car ',
    'car food',
    'car food sun',
    'cat food sun',
    'cat sun'
]

function performSearch(word) {
    return Rx.Observable.defer(() => {
        console.log('sending search for ' + word);
        return Rx.Observable.of({
            word: word,
            results: word.split('')
        });
    })
}

let searchBoxStream = Rx.Observable.interval(500)
    .take(searchBox.length * 2)
    .map((i) => searchBox[i % searchBox.length]);

/** GOOD STUFF STARTS HERE **/
let wordsStream = searchBoxStream
    .map((str) => str.trim().split(' ').filter((w) => w.trim() != ''));

let wordSearchSubjects = [];
let wordSearchStreamSubject = new Rx.ReplaySubject(1);

wordsStream.subscribe((words) => {
    const nWords = words.length;
    const nSubjects = wordSearchSubjects.length;
    // Update streams
    for (i = 0; i < nWords && i < nSubjects; i++) {
        wordSearchSubjects[i].next(words[i]);
    }

    // Create streams
    for (i = nSubjects; i < nWords; i++) {
        const wordSearchSubject = new Rx.ReplaySubject(1);
        wordSearchSubjects.push(wordSearchSubject);
        wordSearchStreamSubject.next(
            wordSearchSubject.asObservable()
                .distinctUntilChanged()
                .flatMap((w) => performSearch(w))
                .concat(Rx.Observable.of(false)) // Ending signal
        )
        wordSearchSubjects[i].next(words[i]);
    }

    // Delete streams
    for (i = nWords; i < nSubjects; i++) {
        wordSearchSubjects[i].complete();
    }
    wordSearchSubjects.length = nWords;
});

let wordSearchStream = wordSearchStreamSubject
    .combineAllCont()
    .map((arr) => arr.filter((r) => r !== false));

resultingStream = wordSearchStream
    .map((arr) => {
        let ret = [];
        arr.forEach(search => {
            ret.push(search.results);
        });
        return ret;
    })
    .subscribe((arr) => console.log(arr));

需要改进的地方:

  1. 我必须使用不等待原始流完成的自定义combineAll实现。如果没有正确实现,这可能会导致内存泄漏,并且应该删除已完成的内部订阅(现在不会)
  2. 必须改进单词更改检测:从中间删除单词会使一半单词再次被搜索。
  3. 可能是一个更好,更直接的解决方案,但我只能想出这个烂摊子。感觉非常hacky /危险。

答案 2 :(得分:0)

我认为解决方案非常明确。首先,我们必须对单词执行diff算法。过时的单词需要删除,只有新单词会被请求。

唯一的真正问题是我们如何以最干净的方式实现它而没有任何数据争用。所以我想出一种实现方式:

searchTags$.pipe(
  scan((pair, tags) => [pair[1] || pair[0], tags], [[]]),
  concatMap(([prevTags, nextTags]) =>
    from(createActions(prevTags, nextTags))
  ),
  reduce((collection$, action) => collection$.pipe(action), of([]))),
  switchMap(stream => stream)
).subscribe(collection => { /*...*/ })

function createActions(prevTags, nextTags) {
  nextTags.reduce((actions, tag) => {
    if (isOld(prevTags, tag)) {
      actions.push(removeAction(tag));
    }
    if (isNew(prevTags, tag)) {
      actions.push(addAction(tag));
    }
    return actions;
  }, []);
}

function removeAction(tag) {
  return map(collection => { collection[tag] = undefined; return collection; });
}

function addAction(tag) {
  return switchMap(collection => requestTag(tag).pipe(map(
    res => { collection[tag] = res; return collection; }
  )));
}

那是我能做的最短的事情!