RxJava中的doOnNext(...)的目的是什么?

75

什么时候我们应该使用Observable中的doOnNext()而不是只使用onNext()?


我注意到你在非 Rx.NET 的问题中使用了 [system.reactive]... 请记住,这是针对 .NET Rx 问题的。专门针对 RxJava 的问题不应该使用它。 - James World
1
我注意到 rxsystem.reactive 的同义词 - 正在尝试在元社区中解决此问题,以便它成为一个单独的更一般性的标签。 - James World
3个回答

108

doOnNext 用于副作用: 在流的中间步骤(例如在流过滤之前)对项进行反应(例如记录日志),以进行横向行为,但您仍希望该值传播到下游。

onNext 更加最终,它会消耗该值。


2
我想知道为什么我不能直接在Observable中放置“log”语句...即调用订阅者/观察者的onNext(...)方法的类。 - Aravind Yarram
当然可以。只是这个值可能会在后续的转换中丢失(例如,在 doOnNext 中记录所有值,然后过滤一些并仅对过滤后的值应用 onNext)。 - Simon Baslé
你会如何称呼 onNext 的对象? - IgorGanapolsky
2
@IgorGanapolsky onNextObserver(或 Subscriber)接口的一部分,通过 subscribe(...) 方法将其插入到流的末端以消费流中的最终值。 - Simon Baslé
1
@SimonBaslé 它运行在哪个调度程序上? - Sebastian Roth
@sebastian和onNext一样,所以如果你使用例如observeOn(x),那就是那个。 - Simon Baslé

27

重要编辑:- 立即在粗体字符下方 -

* 一旦掌握了概念,我强烈建议您查看此link,它可以改变生活,不仅因为我们使用不同的可观察对象,如ObservableSingleMaybe,可能需要不同的工具,如doOnEvent()用于SingledoOnEach()用于Observable,而且如果您想进行调试,则有一些原因使得doOnNext()通常甚至不是理想选择,因为我们可能会忽略其他与解决问题相关的事件*

原始回复:-部分修改-

首先,在Observable和Subscribe之间的操作符链中,doOnNext()可以被调用更多次,这为您提供了更大的调试代码的可能性。由于其“流”本质,因此在RXJava中进行调试并不容易,而doOnNext()使调试更加容易。为此,您还可以考虑将其与doOnError()操作符结合使用。为什么不使用简单的onNext()?因为调试与代码逻辑并不严格相关,理论上您甚至可以在进入生产之前消除doOnNext()

非常重要的一点是,当您订阅长链的 Observable 时,您可以在特定点使用 doOnNext 来查看运算符返回给另一个运算符的内容:
例如:
Observable.just("Donald", "Duck", "Mickey", "Goofy",
                "Uncle")
                .doOnNext{System.out.println("Here ou will get the strings above:$it ")}
                 .map{it.length}
                 .subscribe { println("Here you will get the numbers of how every string is long: $it") }}

一个典型的使用doOnNext()的用例可能是当你想要缓存来自服务器的响应时,你可以使用map()使用doOnNext(),因为它使你的代码更易读,而你只需要放置一个简单的onNext(),理想情况下它应该已经被构造成遵循其他指令。(这是值得商榷的,因为所有的架构思想)

同样地,为了进行相同的调试目的,你可以使用其他自解释操作符:

doOnSubscribe(), doOnUnsubscribe(), doOnCompleted(), doOnError(), doOnTerminate(),finallyDo(), doOnEach(), doOnRequest()

doOnNext()允许您看到Observable(通常非常长的)链中发生了什么,真正重要的是您可以在不影响任何操作、不进行任何转换(比如说,在命令式而不是反应式代码中使用的Log.d类型)的情况下,间接地观察整个链的变化。这就是为什么它被称为副作用的原因。

编辑(因为评论中的问题):

doOnNext()和上面的方法只是回调函数,请参考官方文档doOnNext()就像官方文档所说的那样

只是修改Observable,以便在调用onNext时调用操作。

非常简单,这就是为什么有时称为上传进度条,但也非常适用于存储库模式,例如,如果您想在调用Retrofit后将数据存储到数据库/缓存中。

如果您真的很好奇,doSomethingReactive方法只是在“真正”的方法SomethingReactive内部调用Interface Action的回调方法call()


1
不,我认为这不正确。我添加了一个编辑注释,以使您更容易理解doOnWhatever反应式,请参考上面的内容。 doOnNext在每次调用onNext之前执行!正如您可以从Log的示例中轻松看到的那样,您可以自己制作。 - trocchietto
1
现在这可是一个非常棒的答案。干得好! - Daksh Gargas
我可以在订阅之前多次调用 doOnNext(consumer) 吗?它会对所有的消费者执行 accept() 吗? - Ofek Regev
@OfefRegev,我尝试在订阅之前连续多次调用doOnNext,并且它可以工作。无论如何,这已经超出了问题的范围,即何时使用doOnNext而不是如何使用它。一般来说,如果你玩得够多,这是一个很好的学习方式,从网上的简单示例开始,然后添加多个doOnNext。如果你遇到问题,请写一篇新文章,如果你愿意,可以给我发消息,我会尽力帮助你。 - trocchietto
1
@luis_cortes非常感谢您的介入,当时我还是个新手,现在我已删除了进度条内容,并添加了一些新的重要内容,涉及doOnNext的限制以进行调试(希望不超出OP问题范围)。一旦编写更复杂的链式代码,请务必仔细检查是否合适。 - trocchietto
显示剩余4条评论

2

doOnNext()允许我们在获取新数据项时添加一些额外的操作。

doOnError不处理错误,意思是它不会消耗错误。它只是对其进行某些操作,比如记录它。(对于doOnNext也是如此-它也不会消耗元素,并且该元素仍然会在onNext Subscriber中结束)。


Observable.just("Some data...")
                .doOnNext(System.out::println)
                .doOnNext(value -> System.out.println("before transform: " + value+" You can add something here but it dont affect in data"))
                .map(value -> value + "| adding some new data")
                .doOnNext(value -> System.out.println("after transform: " + value))
                .subscribe(onNext -> {
            System.out.println("onNext: "+onNext); // print out the remaining numbers
        });

结果:

一些数据...

转换前: 一些数据...您可以在此添加一些内容,但不会影响数据

转换后: 一些数据...| 添加一些新数据

onNext: 一些数据...| 添加一些新数据


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接