将许多异步Observable <list>减少为一个Observable <list>

时间:2016-04-17 14:55:47

标签: java multithreading rx-java reactive-programming rx-android

所以这就是我要做的事情:
对于我希望调用一些Service.getList() 10次的每个事件,然后将10个列表合并为一个。

现在我尝试了这两种方式,它们都在UT中工作但在真正的应用程序中失败(我猜我在正版应用程序中给出async http调用时没有正确执行reduce操作)。 对于这两种情况,我都看不到reduce()之后的日志,onNext()onError(),所以我猜测reduce()操作没有完成。

尝试#1:

public Observable<List<Event>> getEventsForLocation(Location location) {
List<Observable<List<Event>>> obs = new ArrayList<>();
for (Venue v : location.getVenues()) {
     obs.add(getEventsForVenue(v)); //does one http call, returns Observable<List<Event>> 
}
return Observable.concat(Observable.from(obs))
            .reduce((List<Event>) new ArrayList<Event>(), (events, events2) -> {
                events.addAll(events2);
                return events;
            })
            .doOnNext(events -> Log.d("reduce ", events.toString()))
            .doOnError(throwable -> Log.e("reduce error", throwable.toString()));}

尝试#2:

public Observable<List<Event>> getEventsForLocation(Location location) {
return Observable
        .from(location.getVenues())
        .flatMap(venue -> getEventsForVenue(venue)) //does one http call, returns Observable<List<Event>> 
        .reduce((List<Event>) new ArrayList<Event>(), (events, events2) -> {
            events.addAll(events2);
            return events;
        })
       .doOnNext(events -> Log.d("service", "total events " + events.toString()))
       .doOnError(t -> Log.e("service", "total events error2 " + t.toString()));}

两种方法都通过的UT:

    @Test
    public void getEventsForLocation() {
        Location loc = new Location("test", newArrayList(new Venue("v1", "url1"),new Venue("v2", "url2")));

        when(httpGateway.downloadWebPage(Mockito.anyString())).thenReturn(
                Observable.just(readResource("eventsForVenue1.html")),
                Observable.just(readResource("eventsForVenue2.html"))
        );

        TestSubscriber<List<Event>> probe = new TestSubscriber<>();
        service.getEventsForLocation(loc).subscribe(probe);

        probe.assertNoErrors();

        //assert the next event containts contents of all lists
        List<Event> events = probe.getOnNextEvents().get(0);

        //first list
        Assert.assertEquals("Unexpected title", "event1", events.get(0).getName());
        Assert.assertEquals("Unexpected artist", "artist1", events.get(0).getArtist());
        //second list
        Assert.assertEquals("Unexpected title", "event2", events.get(1).getName());
        Assert.assertEquals("Unexpected artist", "artist2", events.get(1).getArtist());
    }

更新

以下是使用调度程序的更完整的代码。

Observable
                .just(loc)
                .subscribeOn(AndroidSchedulers.mainThread())
                .observeOn(Schedulers.io())
                .flatMap(location -> service.getEventsForLocation(location))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(getObserver();

3 个答案:

答案 0 :(得分:0)

你可以使用Collect,它可以解决问题。但在这种情况下我合并多个项目,通常我更喜欢使用Scan。对于每个新项目,它将为您提供最后处理的项目。因此,您可以使用之前的项目附加每个项目。

看看大理石图,以防万一适合您的情况 http://reactivex.io/documentation/operators/scan.html

尝试从这个简单示例开始,然后尝试将其应用到您的代码中

/**
 * apply this function for every item against the previous emitted item from the source.
 *  Emitted:
            0
            1
            3
            6
            10
            15
 */
@Test
public void scanObservable() {
    Integer[] numbers = {0, 1, 2, 3, 4, 5};

    Observable.from(numbers)
              .scan((lastItemEmitted, newItem) -> (lastItemEmitted + newItem))
              .subscribe(System.out::println);
}

答案 1 :(得分:0)

我终于找到了一个解决方案,使用zip(),但我不喜欢它 该问题应该可以通过flatMap / reduce

的组合来解决
public Observable<List<Event>> getEventsForLocation(Location location) {
        List<Observable<List<Event>>> venues = new ArrayList<>();
        for (Venue v : location.getVenues()) {
            venues.add(Observable.just(v).flatMap(venue -> getEventsForVenue(venue)));
        }
        return Observable.zip(venues, new FuncN<List<Event>>() {
            @Override
            public List<Event> call(Object... args) {
                List<Event> allEvents = new ArrayList<Event>();
                for (Object o : args) {
                    List<Event> le = (List<Event>) o;
                    allEvents.addAll(le);
                }
                return allEvents;
            }
        });

答案 2 :(得分:0)

如果您不关心列表中的最终订单,可以使用f + from + flatMap + flatMapIterable

toList

如果订单很重要且你想在“parallel”中执行getEventsForVenue,你可以用concatMapEager替换flatMap:

Observable.from(location.getVenues())
.flatMap(venue -> getEventsForVenue(venue))
.flatMapIterable(list -> list)
.toList();