那么,我们如何使我们的Observable具有惰性呢?最简单的技术是用defer()来包装一个eager 的Observable。
public Observable<Person> listPeople() {
return Observable.defer(() ->
Observable.from(query("SELECT * FROM PEOPLE")));
}
defer()接收一个以生成Observable的lambda表达式(一个工厂)。底层的Observable是eager的,所以我们想推迟它的创建。defer()将等待直到最后一刻才真正创建Observable;也就是说,直到有人真正订阅了它。这有一些有趣的暗示。因为Observable是惰性的,因此调用listPeople()没有副作用,而且几乎没有性能占用。还没有查询数据库呢。您可以将Observable<Person>视为一个承诺,但没有任何后台处理。请注意,目前没有异步行为,只是延迟评估。这类似于Haskell编程语言中的值仅在绝对需要时才被延迟评估。
如果你从未用函数式语言编程,你可能会很困惑为什么惰性如此重要和具有开创性。事实证明,这种行为非常有用,可以大大提高实现的质量和自由。例如,您不再需要注意获取、何时、以什么顺序获取资源。RxJava只在需要时才加载它们。
举个例子,我们已经看到过很多次的这种简单的回退机制:
void bestBookFor(Person person) {
Book book;
try {
book = recommend(person);
} catch (Exception e) {
book = bestSeller();
}
display(book.getTitle());
}
void display(String title) {
//...
}
你可能认为这样的结构没有什么错。在这个例子中,我们试图为一个特定的人推荐最好的书(recommend),但是万一失败,我们会优雅地降级并显示出畅销书(bestSeller)。假定获取畅销书(bestSeller)的速度更快,而且可以缓存。但是,如果您可以以声明方式添加错误处理,以便try - catch块不会模糊真实逻辑,那这该怎么办呢?
void bestBookFor(Person person) {
Observable<Book> recommended = recommend(person);
Observable<Book> bestSeller = bestSeller();
Observable<Book> book = recommended.onErrorResumeNext(bestSeller);
Observable<String> title = book.map(Book::getTitle);
title.subscribe(this::display);
}
到目前为止,我们只研究RxJava,因此我保留了所有这些中间值和类型。在实际生产中,bestBookFor()看起来更像这样:
void bestBookFor(Person person) {
recommend(person)
.onErrorResumeNext(bestSeller())
.map(Book::getTitle)
.subscribe(this::display);
}
这段代码简洁而易读。首先为某人找到一个推荐(即上文说的针对某个特定的人所作出的推荐好书)。如果出现错误(onErrorResumeNext),那么继续使用畅销书降级代替(bestseller)。无论哪一个成功,map通过提取title来返回值,并显示它。onErrorResumeNext()是一个强大的操作符,它拦截了上游的异常,并吞下它们,并订阅了提供的备份的Observable。这就是Rx如何实现try - catch子句的。在本书的247页的“Declarative try-catch Replacement”上,我们将花更多的时间在错误上处理上。就目前而言,请注意我们如何可以惰性地调用bestSeller(),而不必担心获取畅销书的发生,尤其是当获取读者推荐成功时候。