How to propagate errors through catchError() properly? - rxjs

I wrote a function that is pipe-able:
HandleHttpBasicError<T>()
{
return ((source:Observable<T>) => {
return source.pipe(
catchError((err:any) => {
let msg = '';
if(err && err instanceof HttpErrorResponse)
{
if(err.status == 0)
msg += "The server didn't respond";
}
throw {
err,
msg
} as CustomError
})
)
})
}
I can use this function this way in my HttpService:
checkExist(id:string)
{
return this.http.head<void>(environment.apiUrl + 'some_url/' + id)
.pipe(
HandleHttpBasicError(),
catchError((err:CustomError) => {
if(err.msg)
throw err.msg;
if(err.err.status == HttpStatusCodes.NOT_FOUND)
throw("It doesn't exist.");
throw(err);
})
)
}
It's working great. When I subscribe to checkExist(), I get a good error message, because HandleHttpBasicError first catches an error and throws it to the service's catchError(), which throwes the error message because it was not null.
This way, it allows me to have a global catchError() which handles error messages that will always be the same. In the future, I will do it in a HttpHandler, but that's not the point here.
Is it ok to chain the errors with the throw keyword?
I tried to return Observable.throwError(), but the browser said
Observable.throwError is not a function
My imports are import {Observable, of, throwError} from 'rxjs';.
Isn't it better to do this:
return ((source:Observable<T>) => {
return source.pipe(
catchError((err:any) => {
msg = '';
...
return of({err, msg} as CustomError)
/* instead of
throw(err)
-or-
return Observable.throwError(err) (which doesn't work)
*/
})
)
})
?

Is it ok to chain the errors with the throw keyword ?
Yes, it's totally fine. rxjs try-catches such cases and converts it into an error notification.
I tryed to return Observable.throwError() but the browser say "Observable.throwError is not a function"
With rxjs6, the Observable prototype is no longer modified to contain operators or these »creation operators«, instead they are exposed as standalone functions. You can read more about it here, but the gist of it is that you'd just return throwError(…), e.g.
return source$.pipe(
catchError(err => err.code === 404
? throwError("Not found")
: throwError(err)
)
)

Related

RxSwift: Calling onCompleted after onNext delivers only the completed event

I'm wrapping some legacy completion-block code in an Observable. It will emit one event (next or error), and then complete. The problem is that calling onNext(), onCompleted() only sends the completed event to the observer. Why doesn't the next event get delivered?
UPDATE: The people stream actually works as expected. The issue turns out to be in the next stream, filteredPeople. The inner completed event is passed along to it, and I'm just returning it, which terminates the stream.
I need to filter out completed events from inner streams.
let people = Observable<Event<[Person]>>()
.flatMapLatest {
return fetchPeople().asObservable().materialize()
}
.share()
// this is bound to a search field
let filterText = PublishSubject<String>()
let filteredPeople = Observable.combineLatest(people, filterText) { peopleEvent, filter in
// this is the problem. the completed event from people is being returned, and it terminates the stream
guard let people = peopleEvent.element else { return peopleEvent }
if filterText.isEmpty { return .next(people) }
return .next(people.filter { ... })
}
func fetchPeople() -> Single<[Person]> {
return Single<[Person]>.create { observer in
PeopleService.fetch { result in
switch result {
case .success(let people):
observer(.success(people))
case .failure(let error):
observer(.error(error))
}
}
return Disposables.create()
}
}
filteredPeople.subscribe(
onNext: { event in
// ?! doesn't get called
},
onCompleted: {
// we get here, but why?
},
onError: {event in
...
}).disposed(by: disposeBag)
You haven't posted the code that is causing the problem. The code below works as expected:
struct Person { }
class PeopleService {
static func fetch(_ result: #escaping (Result<[Person], Error>) -> Void) {
result(.success([]))
}
}
let disposeBag = DisposeBag()
func fetchPeople() -> Single<[Person]> {
return Single<[Person]>.create { observer in
PeopleService.fetch { result in
switch result {
case .success(let people):
observer(.success(people))
case .failure(let error):
observer(.error(error))
}
}
return Disposables.create()
}
}
let people = Observable<Void>.just(())
.flatMapLatest { _ in
return fetchPeople().asObservable().materialize()
}
.share()
people.subscribe(
onNext: { event in
print("onNext does get called")
print("in fact, it will get called twice, once with a .next(.next([Person])) event")
print("and once with a .next(.completed) event.")
},
onCompleted: {
print("this prints after onNext gets called")
})
.disposed(by: disposeBag)
I fixed it by filtering out completed events from the inner stream. I am not sure this is the right way, but I can't think of a better solution.
let people = Observable<Event<[Person]>>()
.flatMapLatest {
return fetchPeople()
.asObservable()
.materialize()
// Our work is done, but don't end the parent stream
.filter { !$0.isCompleted }
}
.share()

correct use of retryWhen operator with RxSwift 4.0.0

with RxSwift 3.6.1 I made this extension to ObservableType to get a new token after an error request:
public extension ObservableType where E == Response {
public func retryWithToken() -> Observable<E> {
return retryWhen { error -> Observable<Response> in
return error.flatMap({ (error) -> Observable<Response> in
if let myApiError: MyApiError = error as? MyApiError {
if (myApiError == MyApiError.tokenError) {
return Session.shared.myProvider.request(.generateToken)
} else {
return Observable.error(myApiError)
}
}
return Observable.error(error)
})
}
}
}
and then I can use it:
Session.shared.myProvider.rx
.request(.mySampleRequest)
.filterSuccessfulStatusCodes()
.retryWithToken()
.subscribe { event in
....
}.disposed(by: self.disposeBag)
but with RxSwift 4.0.0 now the sequence expect a
PrimitiveSequence<SingleTrait, Response>
someone can explain to me how to do the same with RxSwift 4.0.0? I try with an extension to PrimitiveSequence but I've some compilation errors.
I believe that has nothing to do with RxSwift but is a Moya change. MoyaProvider.rx.request returns Single which is a typealias for PrimitiveSequence which is not an ObservableType.
You declare your function upon the ObservableType.
So just do asObservable() before retryWithToken()

Debugging Jasmine Custom Matcher Messages

I have the following code for a Jasmine Custom Matcher, as described here:
jasmine.addMatchers({
testingFunction: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
if (expected === undefined) {
expected = '';
}
var result = {};
result.pass = util.equals(actual.myValue, 1, customEqualityTesters);
if (result.pass) {
result.message = "Passed";
} else {
result.message = "Failed";
}
return result;
}
}
}
});
And calling it as such:
.then(function() {
expect({
myValue: 1
}).testingFunction();
})
During debugging, I see that execution goes to my custom matcher but for some reason, neither my Pass or Fail messages get printed to the console.
Any ideas as to why this might be?
Thanks
For anyone else that may be running into this issue, I figured out that in my jasmineNodeOpts, I was overriding the jasmine print method as such:
// Overrides jasmine's print method to report dot syntax for custom reports
//print: () => {},
Removing that fixed my issue.

Reconnect to source and continue from last emitted value

I'm trying to figure out how to implement reconnect to observable after a transient failure, to continue from a last emitted value.
Assume I have the following method:
interface MyQuery {
fromId: number;
toId: number;
}
interface MyItem {
id: number;
val: string;
}
function observeUnstable(query: MyQuery): Observable<MyItem>;
The method observableUnstable lets to subscribe to a stream which emits values and may emit the following error in case of intermittent connection failure:
class DisconnectedError extends Error;
I want to compose a new observable which would wrap the original observable above and have transparent resubscribe from the position at which the previous subscription has failed.
The data types are going to be opaque, so I would want to make the reconnection logic generic, probably as an operator which would accept a high order selector function:
let startQuery = { fromId: 1, toId: 10 };
let reconnectable = observeUnstable(startQuery)
.lift(new ReconnectOperator<MyItem>((err, lastValue?) => {
if (err instanceof DisconnectedError) {
// This error indicates that we've been disconnected,
// resubscribing from the place we have stopped
let continueQuery = {
fromId: lastValue ? lastValue.id + 1 : startQuery.fromId,
toId: startQuery.toId
};
return observeUnstable(continueQuery);
} else {
// Rethrowing error we don't expect
throw err;
}
}));
Here are my ReconnectOperator and ReconnectSubscriber:
class ReconnectOperator<T> implements Operator<T, T> {
constructor(private handler: (err: any, lastValue?: T) => Observable<T>) {
}
call(subscriber: Subscriber<T>, source: any): any {
return source.subscribe(new ReconnectSubscriber(subscriber, this.handler));
}
}
class ReconnectSubscriber<T> extends Subscriber<T> {
private lastValue?: T;
constructor(destination: Subscriber<T>, private handler: (err: any, lastValue?: T) => Observable<T>) {
super(destination);
}
protected _next(value: T) {
this.lastValue = value;
super._next(value);
}
error(err: any) {
if (!this.isStopped) {
let result: Observable<T>;
try {
result = this.handler(err, this.lastValue);
} catch (err2) {
super.error(err2);
return;
}
// TODO: ???
result.subscribe(this._unsubscribeAndRecycle());
// this._unsubscribeAndRecycle();
//this.source.subscribe(result);
//this.add(subscribeToResult(this, result));
}
}
}
This subscriber is very similar to CatchSubscriber with only one difference is that CatchSubscriber returns original observable in selector method, in my case I want to return last value so the selector could use it to compose a brand new observable rather than reusing the original one.
But I messed with resubscribe logic somehow so the resulting observable never returns complete for small amount of test values, and crashes with stack overflow for bigger amount of test values.
Also, my idea here is to implement a new operator but if it's possible to implement it in a single method just using composition of existing operators, in a generic way, that would be even better :)
Example of an alternative method but without operator:
function observeStable<T, Q>(
query: Q,
continueQueryFunc: (query: Q, lastValue?: T) => Observable<T>
): Observable<T> {
return observeUnstable<T>(query).catch((err, ???) =>
if (err instanceof DisconnectedError) {
let lastValue = ???
let continueQuery = continueQueryFunc(query, lastValue);
return observeUnstable(continueQuery);
} else {
throw err;
}
);
}

How to Pipe Observables

I want to test every value an observable emits, and if it fits certain criteria, then error-out the result, otherwise pass the value on. Is there an operator for this?
If you use throw, you can separate cases that do not match the condition, but they will terminate immediately.
let source = Observable.of(1,2,3);
source
.mergeMap(value => {
if (value > 1) { // condition
return Observable.throw(`Out Of Condition: ${value}`);
}
return Observable.of(value);
})
.subscribe(
value => console.log(`Next: ${value}`),
error => console.log(`Error: ${error}`),
() => console.log('completed')
);
Result:
Next: 1
Error: Out Of Condition: 2
You can also think of ways like stdout and stderr in bash. For this, additional information was augmented. In this case, it does not end in the middle.
const stdout = 1;
const stderr = 2;
let source = Observable.of(1,2,3);
source
.map(value => {
if (value > 1) { // condition
return [stderr, value];
}
return [stdout, value];
})
.subscribe(channel_value => {
let channel = channel_value[0];
let value = channel_value[1];
if (channel == stdout) {
console.log(`stdout: ${value}`);
}
else if (channel == stderr) {
console.log(`stderr: ${value}`);
}
});
Result:
stdout: 1
stderr: 2
stderr: 3
I don't know if this is the right answer, but what worked for me, was Observable.create
Observable.create((observer: Observer) =>
sourceObservable.subscribe((val: Value) => {
if(condition)
observer.next(val);
else
observer.error(val);
}, observer.error, observer.complete)
)
This is very inelegant. I hope there is a better way

Resources