Skip to content

Observable-aware version of doOnNext for handling asynchronous side-effects #3989

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
tednaleid opened this issue Jun 5, 2016 · 2 comments
Closed
Labels

Comments

@tednaleid
Copy link

doOnNext is useful for doing side-effects in a synchronous manner, but sometimes I want to perform a side-effect on my stream using a function that returns an Observable. If the side-effect fails, I want to be notified of that downstream, but if it succeeds, I want the stream to continue with the original object.

The use-case that I've got on my current project is doing an asynchronous save of an object followed by publishing a notification of the updated object on one or more message queue topics. I want the saved object to be returned to callers of the save method. Currently, I need to do something nested like this (groovy code):

public Observable<Widget> saveAndPublish(Widget widget) {
    return save(widget)
        .flatMap { Widget savedWidget ->
            return publishNotification(savedWidget)
                .map { NotificationResult ignore -> savedWidget }
        }
}

public Observable<Widget> save(Widget widget) { ... }
public Observable<NotificationResult> publishNotification(Widget widget) { ... }

And that code works if there is only one notification returned per Widget, otherwise it'd emit savedWidget once for every notification in the stream.

If I need to ignore multiple notifications, I need to do:

public Observable<Widget> saveAndPublish(Widget widget) {
    return save(widget)
        .flatMap { Widget savedWidget ->
            return publishNotification(savedWidget)
                    .ignoreElements()
                    .cast(Widget)
                    .defaultIfEmpty(savedWidget)
        }
}

If the notifications had been synchronous, I could have used doOnNext:

public Observable<Widget> saveAndPublish(Widget widget) {
    return save(widget)
        .doOnNext(this::publishNotification);
}

But that doesn't work because the notifications return an Observable.

This pattern has been common enough in my code that I created a groovy extension jar that has a method in it called flatTap that does asynchronous side-effects without changing the stream. It lets me change my code from the above to:

public Observable<Widget> saveAndPublish(Widget widget) {
    return save(widget)
        .flatTap(this::publishNotification);
}

This feels like a gap to me in RxJava. I've got map for synchronously changing the stream, and flatMap for asynchronously changing it. I've got doOnNext for synchronous side effects that don't modify the stream, but no equivalent to flatMap for doing asynchronous side effects.

If there are others that would find this useful, I'd be willing to work on putting together a PR to integrate this into RxJava.

I'm also open to changing the naming of it. In my jar, I aliased doOnNext to tap (which is a method in Ruby which does the same thing) and called my asynch method flatTap as I liked the symmetry of:

map : flatMap :: tap : flatTap

but I can see how that might not fit within the existing conventions of Rx.

@akarnokd
Copy link
Member

This is an use case for flatMap plus compose if a specific set of operatos appear together. I'm not convinced this should be in RxJava because it is likely different users want to have different handing of the tapped source.

@tednaleid
Copy link
Author

I just found #2931 which is a closed issue that looks like a more generic version of what I'm proposing here. Based on the discussion there and the comment above, it sounds like the RxJava way is that using nested lambdas with flatMap and compose are the suggested route.

I find nested chains to be harder to read and reason about, but I understand that there need to be a point where a library stops adding sugar and asks it's users to use the tools it's been given.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants