I'm using project reactor and I have a very long flow in which I get an exception (when parsing a string to json with Jackson). The thing is that even though I use
.map(this::parser)
.onErrorResume(err -> {
log.error(myMsg);
return Mono.empty();
})
.flatMap(writeToPulsar)
.subscribe()
The flow won't continue. I do see the error log and the flow doesn't throw an exception, but the flow won't continue to get executed. Any reason for this to happen?
When I change the code to the (unwanted) .onErrorContinue(), the data pipeline won't get stopped:
.map(this::parser)
.onErrorContinue((err, msg) -> {
log.error(myMsg);
})
.flatMap(writeToPulsar)
.subscribe()
As a part of the error handling you are returning Mono.empty() and it means your flow will be completed without emitting any result and flatMap will not be executed.
Not sure about the expected behavior but if you want to continue the flow - return some "default" value from onErrorResume instead or use switchIfEmpty operator to provide another publisher.
Related
Can someone explain to me why this is not getting caught by the catch block, I am thinking its because I have a switch map, which would unsubscribe from the first observable. Have commented it out with no luck. The first observable (this.apiPaymentService.deleteStoredPaymentMethod()) is the one causing a 403
try {
this.apiPaymentService.deleteStoredPaymentMethod()
.pipe(
switchMap(() => this.apiPaymentService.getStoredAchPaymentMethods(),
catchError((err) => { return throwError(err)})
).subscribe()
} catch (error) {
console.log('I am never called ')
this.errorModal(error);
}
First: Make sure your code parses
If I throw this into my IDE, I get a parsing error immediately. I'll assume you don't want an operator as a second parameter to switchMap and that you've simply forgotten an ) near the end of the line.
What's happening inside your try block?
Inside your try block, you subscribe to an observable. That's it. That's what happens there. The subscription happens without an error. So your try-catch will complete without an error.
If this.apiPaymentService.getStoredAchPaymentMethods() is asyncronous, the try block will have completed (and may not even be on the call stack anymore) before getStoredAchPaymentMethods() resolves with a value.
How to manage Asynchronous Errors
You're already most of the way there! Instead of catching and then rethrowing an error, you can manage it right there.
The following is a no-op:
catchError((err) => { return throwError(err)})
But you can return something else instead, or you can retry or something like that :)
Aside: not just observables
Here's some old-skool code with the same underlying phenomenon.
function throwErrorFunction() {
console.log("Throwing an error!");
throw 'Throwing an error from my throw error function';
}
try {
setTimeout(throwErrorFunction, 5000);
} catch (error) {
console.log('I am never called ');
}
Here's a way to think about this. The code above never calls the throwErrorFunction. Function application in JavaScript is done wiht parenthesis. So you'd need something like throwErrorFunction() somewhere in order to call the function. But that never happens here.
But somehow the console will still read "Throwing an error!". So how does that happen if we've not called throwErrorFunction? Well... setTimeout eventually calls throwErrorFunction for us! In this case, it waits 5 seconds and then calls the function.
But by the time five seconds have elapsed, the try-catch statement has already been executed. Same deal as before! :)
I have a List<String> myList. The code looks like:
Flux.fromIterable(myList)
.distinct()
.flatMap(id -> mysvc.getItem(id)
.switchIfEmpty(Mono.error(...))))
Note that mysvc is returning a Mono<Item> and the .switchIfEmpty is attached to THAT CALL, NOT the flatmap as mysvc will return a Mono.empty() if id is invalid.
Right now, it is throwing an error if an id is invalid as expected, but it throws them one at a time. If you have 2 errors, you'll only see the first one until you fix it, and then you'll see the next one and so on.
Ideally, I'd like to somehow collect all the bad ids and display a single error message otherwise return the Flux<Item>.
if i wrap blocking code into a flatMap, is this still a non-blocking operation ?
Example:
public Mono<String> foo() {
Mono.empty().flatMap(obj -> {
try {
Object temp = f.get();//are the thread at this point blocked or not ?
} catch (Exception e) {
e.printStackTrace();
throw e;
}
return Mono.just("test");
});
So, i think when i wrap blocking code into reactive code, the operation is still non-blocking ? If i am wrong, pls explain it to me.
if i wrap blocking code into a flatMap, is this still a non-blocking operation ?
flatMap doesn't create new threads for you. For example:
Mono.just("abc").flatMap(val -> Mono.just("cba")).subscribe();
All the code above will be executed by the current thread that called subscribe. So if the mapper function contained a long blocking operation the thread that called subscribe will be blocked as well.
To transform this to an asynchronous operation you can use subscribeOn(Schedulers.elastic());
Mono.just("abc").flatMap(val -> Mono.just("cba")).subscribeOn(Schedulers.elastic());
Mono and Flux don't create threads, but some operators take Scheduler as an extra argument to use such as the interval operator, or alter threading model all together such as subscribeOn.
One extra thing, in your example the mapper function is never going to be called, since your applying flatMap to an empty mono which completes directly with no values emitted.
For example
return Observable.forkJoin([
getData1(),
getData2(),
getData3(),
getData4()
])
If one of them fails, the subscriber that subscribes to the forkJoin observable fails, is there an operator that only fails if all of them fails?
It's part of the design of rxjs that ANY error causes the chain of observables to fail.
It sounds like the condition you are trying to trap is not really a failure, but a normal case - if you change the sub-observables to detect the error and then return a value which can be detected down stream with something like a map or a filter, then you will be able to achieve what you are looking for.
if you're interested in getting error from inner observables, materialize might be way to go:
const pluckError = (obs: Observable<any>) =>
obs.materialize().filter(x => x.kind === 'E')
Observable.forkJoin(
obs1.pipe(pluckError),
obs2.pipe(pluckError)
....
)
It is important to note outer observable shouldn't raise error as it'll terminate whole observable, but you can catch / or get Notification metadata using materialize and only pick up error to make forkjoin completes.
I'm somewhat familiar with basic RxJS concepts like Observables, Observers and Subjects but RxJS Notifications concept is completely new to me.
What is it for? When should I use it?
The documentation you quoted mentions :
This class is particularly useful for operators that manage notifications, like materialize, dematerialize, observeOn, and others. Besides wrapping the actual delivered value, it also annotates it with metadata of, for instance, what type of push message it is (next, error, or complete).
So the question turns out to be about use cases for materialize and the like.
Basically, you use materialize to get meta-information about the dataflow without incurring into the associated side-effects (an error incurring in a stream for example propagates, a stream which completes can lead to the completion of other streams etc.). dematerialize allows to restore the side-effects.
Here are uses case from former SO questions :
Receiving done notifications from observables built using switch
RxJs - parse file, group lines by topics, but I miss the end
A use case: as errors or completions are propagated immediately, you can't for example delay them. To do so, you can try this approach:
// sample stream
interval(500).pipe(
mapTo('normal value'),
// sometimes value, sometimes throw
map(v => {
if (randomInt() > 50) {
throw new Error('boom!')
} else return v;
}),
materialize(),
// turns Observable<T> into Notification<Observable<T>>
// so we can delay or what you want
delay(500),
// and we need to do some magic and change Notification of error into
// Notification of value (error message)
map(n => n.hasValue? n : new Notification('N', n.error.message, null)),
// back to normal
dematerialize()
)
// now it never throw so in console we will have
// `normal value` or `boom!` but all as... normal values (next() emmision)
// and delay() works as expected
.subscribe(v => console.log(v))