Подписка на вложенную Observable

У меня есть приложение, которое делает один запрос http, чтобы получить список элементов, а затем делает запрос http для каждого элемента в списке, чтобы получить более подробную информацию о каждом элементе. Эффективно:

class ItemsService {
  fetchItems() {
    return this.http.get(url)
    .map(res => res.json())
    .map(items => items.map(this.fetchItem(item)));
  }

  fetchItem(item: Item) {
    this.http.get(`${url}/${item.id}`)
    .map(res => res.json());
  }
}

Тогда я сделаю что-то вроде itemsService.fetchItems().subscribe(items => console.log(items)) но в конечном итоге я получаю массив наблюдаемых (каждый ответ от fetchItem). Мне нужно подписаться на каждую из внутренних наблюдаемых, чтобы fetchItem запрос на самом деле срабатывает.

Я также пытался использовать flatMap вместо карты, но, похоже, в этом случае тот же результат. Есть ли способ для подписки на вложенные наблюдаемые?

3 ответа

Решение

Я бы сделал это следующим образом:

function mockRequest() {
    return Observable.of('[{"id": 1}, {"id": 2}, {"id": 3}]');
}
function otherMockRequest(id) {
    return Observable.of(`{"id":${id}, "desc": "description ${id}"}`);
}

class ItemsService {
    fetchItems() {
        return mockRequest()
            .map(res => JSON.parse(res))
            .concatAll()
            .mergeMap(item => this.fetchItem(item));
    }

    fetchItem(item: Item) {
        return otherMockRequest(item.id)
            .map(res => JSON.parse(res));
    }
}

let service = new ItemsService();
service.fetchItems().subscribe(val => console.log(val));

Смотрите живую демоверсию: http://plnkr.co/edit/LPXfqxVsI6Ja2J7RpDYl?p=preview

Я использую трюк с .concatAll() преобразовать массив объектов, таких как [{"id": 1}, {"id": 2}, {"id": 3}] на отдельные значения, излучаемые по одному {"id": 1}, {"id": 2} а также {"id": 3} (на данный момент это недокументированная функция). Тогда я использую mergeMap() получить их содержимое в отдельном запросе и объединить его результат в цепочку операторов.

Этот пример plnkr выводит на консоль:

{ id: 1, desc: 'description 1' }
{ id: 2, desc: 'description 2' }
{ id: 3, desc: 'description 3' }

Проблема, с которой вы, вероятно, столкнулись, заключается в том, что вы недостаточно сглажены.

flatMap или же mergeMap сгладит Observables, Promises, Arrays, четное generators (не цитируйте меня по этому последнему), почти все, что вы хотите бросить в него.

Итак, когда вы делаете .flatMap(items => items.map(item => this.fetchItem(item)) ты действительно просто делаешь Observable<Array<Item>> => Observable<Observable<Item>>

Когда вы просто делаете map ты делаешь Observable<Array<Item>> => Observable<Array<Observable<Item>>>,

Что вам нужно сделать, это сначала сгладить массив, а затем сгладить каждый запрос:

class ItemsService {
  fetchItems() {
    return this.http.get(url)
    .map(res => res.json())
    // Implicitly map Array into Observable and flatten it
    .flatMap(items => items)
    // Flatten the response from each item
    .flatMap((item: Item) => this.fetchItem(item));
  }
}

Теперь вышесказанное работает, если вы не возражаете против получения каждого ответа по отдельности. Если вам нужно получить все предметы, вы должны использовать forkJoin на все внутренние ценности, но вам все равно нужно flatMap чтобы сгладить полученное внутреннее значение:

fetchItems(): Observable<Response[]> {
  return this.http.get(url)
    .map(res => res.json())
    .flatMap(items => {
       const requests = items.map(item => this.fetchItem(item));
       return Rx.Observable.forkJoin(requests);
    });
}

Вы можете разбить массив элементов перед строкой, которая вызывает this.fetchItem, Вы можете использовать mergeMap для Observable, значение которого является массивом, и каждый элемент будет генерироваться индивидуально.

fetchItems() {
    return this.http.get(url)
       .map(res => res.json())
       .mergeMap(arrItem => this.fetchItem(arrItem));
}

Изменить: я думаю, я должен был предоставить больше объяснений. mergeMap является синонимом flatMap в rxjs. Как правило, вы используете flatMap, когда ваша проекционная функция возвращает Observable, но она также сгладит массивы, поэтому вызов mergeMap будет генерировать каждый элемент отдельно, я думал, что это то, чего хотел достичь OP. Я также понял, что вы можете объединить mergeMap звонок и последний map звоните, потому что прогноз для mergeMap будет вызываться для каждого элемента в массиве, я внес изменения в коде выше.

Другие вопросы по тегам