Как объединить несколько массивов типа Observable Observable? - PullRequest
0 голосов
/ 23 мая 2019

Я выполняю несколько запросов в Firebase Firestore.Они все (могут) вернуть Observable.Я хотел бы объединить все Observable в ONE Observable.

Я пробовал разные операторы RxJS, например;объединить последние, forkJoin, concat, zip, объединить и объединить карту.Но я не смог достичь желаемого результата.

getPrivateBooksFromAuthors(authors): Observable<Book[]> {
    let bookRefs: Observable<Book[]>[] = [];
    authors.map(key => {
      bookRefs.push(this.afs.collection<Book>('books', ref => 
        ref.where('public', '==', false)
           .where('authors', 'array-contains', key)
        ).valueChanges())
    });
    return bookRefs[0]
}

В приведенном выше фрагменте кода я получаю все частные книги от авторов [0].Когда я возвращаю concat (... bookRefs) или concat (bookRefs [0], bookRefs [1]), я все равно получаю книги только от авторов [0].Я ожидаю получить все книги от всех авторов.

Ответы [ 3 ]

1 голос
/ 23 мая 2019

Я думаю, что лучшее решение - это использовать fork join .

Fork позволяет выполнять вызов параллельно. Результат каждого вызова помещается в один объект (join), и вы можете получить результат всего вызова.

0 голосов
/ 27 мая 2019

@ Дофламинго указал нам в направлении forkJoin, а @ Llorenç привел отличный пример кода с картой и уменьшил количество каналов, чтобы объединить Observables в один Observable. Для меня forkJoin потерпел неудачу, если у некоторых авторов не было Книг. Так что я закончил тем, что пошел с CombineLatest (который не ждет завершения всех наблюдаемых). Это мой окончательный код (вся заслуга @ Llorenç):

  mockBooks$(key): Observable<Book[]> {
    return this.afs.collection<Book>('books', ref => 
      ref.where('private', '==', true)
         .where('authors', 'array-contains', key)
      ).valueChanges()
    // return of([key + '-book1', key + '-book2', key + '-book3']);
  }

  getPrivateBooksFromAuthors(authors): Observable<Book[]> {
    let bookRefs: Observable<Book[]>[] = authors.map(key => this.mockBooks$(key));

     // return combineLatest(bookRefs).pipe(
     //     tap((books) => console.log('After forkJoin', books)),
     //     // You need this flattening operation!!
     //     map(books => books.reduce((acc, cur) => [...acc, ...cur], []) ));

     return combineLatest<Book[]>(bookRefs).pipe(
        map(arr => arr.reduce((acc, cur) => acc.concat(cur) ) ),
     )
  }

Я закомментировал код Ллоренса, чтобы показать разницу. Спасибо вам обоим!

0 голосов
/ 23 мая 2019

Как сказал @Doflamingo, вы можете использовать forkJoin для параллельного вызова их и получения ответа с массивом всех разрешенных ответов. Ваша проблема в том, что каждый ответ - это Book [], поэтому в forkJoin вы получаете массив Book [] (Book [] []). Вам нужно разложить Книгу [] [] в Книгу []. Вы можете сделать это, используя карту и функцию уменьшения .

Здесь я разместил простой фрагмент кода, в котором консоль записывает отклик forkJoin и результат после применения функции Reduce.

function mockBooks$(key): Observable<Book[]> {
  return rxjs.of([key + '-book1', key + '-book2', key + '-book3']);
}

function getPrivateBooksFromAuthors(authors): Observable<Book[]> {
    let bookRefs: Observable<Book[]>[] = authors.map(key => mockBooks$(key));

    return rxjs.forkJoin(bookRefs).pipe(
        rxjs.operators.tap((books) => console.log('After forkJoin', books)),
        // You need this flattening operation!!
        rxjs.operators.map(books => books.reduce((acc, cur) => [...acc, ...cur], []) ));
}


const authors = ['a1', 'a2', 'a3', 'a4', 'a5'];
getPrivateBooksFromAuthors(authors).subscribe((data) => console.log('Final result', data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.js"></script>

Надеюсь, это поможет!

...