When speaking about Observables (especially rxjs), what is the difference between "finally" and "done" or "complete"?
Finally always happens whenever an observable sequence terminates (including errors); completed only happens when it terminates without errors.
Finally:
Invokes a specified action after the source observable sequence
terminates gracefully or exceptionally.
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/finally.md
OnCompleted:
An Observable calls this method after it has called onNext for the
final time, if it has not encountered any errors.
http://reactivex.io/documentation/observable.html
"Done" isn't an rx/observables concept. I've just seen it printed in examples of "Complete" / "OnComplete".
Note: when you call subscribe, the syntax is usually:
observable.subscribe([observer] | [onNext], [onError], [onCompleted]);
// Like this:
observable.subscribe(
(value) => { ... },
(error) => { ... },
() => { console.log('complete!'); }
);
or
observable.subscribe({
next: x => console.log('got value ' + x),
error: err => console.error('something wrong occurred: ' + err),
complete: () => console.log('done'),
});
Whereas finally is handled like this:
observable.finally(() => { console.log('finally!'); })
.subscribe(...) // you can still call subscribe
To be more precise, the finally() operator adds a dispose handler. The complete notification just calls the complete handler in observers.
What this means in practise:
When using finally() the callback is going to be called in every situation that causes unsubscription. That's when complete and error notifications are received by observers but also when you manually unsubscribe.
See demo: https://jsbin.com/dasexol/edit?js,console
complete or error handlers are called only when the appropriate notification is received. Only 0 - 1 handlers can be called but never both of them.
Related
I thought that by using a fakeAsync wrapper, my tests wouldn't automatically run subscriptions and that I'd be controlling that part by calling tick manually, but that doesn't seem to be the case. For example, using this method:
foo(): void {
of([1, 2, 3]).subscribe({
next: () => {
console.info('Subscription completed.')
this.value = true
},
error: (err: unknown) => console.error('error called')
})
}
and testing with this spec:
it('should foo', fakeAsync(() => {
component.foo()
expect(component.value).toBeFalse()
}))
I'm seeing the subscription completed message print and thus the expectation fails. I thought that the foo method would be called, but that the subscription wouldn't complete until I put a tick() call into the spec.
What am I doing wrong?
Your assumption that all observables are asynchronous is wrong.
Observables can either be asynchronous or synchronous depending on the underlying implementation. In other words, reactive streams are synchronous if the source is synchronous unless you explicitly make them asynchronous.
Some examples:
//Synchronous
of(1,2)
.subscribe(console.log);
//asynchronous because of async source
interval(1000)
.subscribe(console.log);
//aynchronous because one stream is async (interval)
of(1,2)
.pipe(
mergeMap(x => interval(1000).pipe(take(2)))
)
.subscribe(console.log);
//async because we make it async
of(1,2, asyncScheduler)
.subscribe(console.log);
TLDR: Because of([1, 2, 3]) is a synchronous source, you won't be able to control when it completes with fakeAsync.
tick is just the way to control the passage of time. from Angular documentation.
If you omit the tickOptions parameter (tick(100))), then tickOptions defaults to {processNewMacroTasksSynchronously: true}.
https://angular.io/api/core/testing/tick
When dealing with concatMap, how can I abort the execution of observables further down the line and prevent calling the completion handler?
Here is a simple example.
of(...[1, 2, 3]).pipe(
concatMap(t => of(t)),
map(n => {
console.log(n);
if (n === 2) {
throw new Error('OK, fail here');
}
}),
catchError((e, c) => of(console.log('Caught ' + e)))
)
.subscribe(
{
complete: () => console.log('Complete should not be triggered in an error case, but here it is'),
error: (err: any) => {
console.log('I did never trigger, thats ok!');
}
});
A source observable emits 1,2,3 which is piped into concatMap.
There are two possible scenarios: All three observables are emitted without error in this case complete handler should be triggered. Or the other case depicted here: Somewhere down the line there is an error like when n === 2. ConcatMap stops executing the next observable which is perfect but it still triggers the completion handler, which is undesired.
Actual result
1
2
Caught Error: OK, fail here
Complete should not be triggered in an error case, but here it is
Desired result
1
2
Caught Error: OK, fail here
Any hints? My previous attempt was to throw in the next handler but that turned out to be really bad :/ as it triggered a hostReportError
Ok,
since I was able to answer all the other fun questions regarding RxJS by myself, here we go :)
Remember: catchError replaces the faulted observable by some other observable. If catchError would therefore return an observable via throwError it would halt the execution and even better, it would trigger the error handler that was idle before.
of(...[1, 2, 3]).pipe(
concatMap(t => of(t)),
map(n => {
console.log(n);
if (n === 2) {
throw new Error('OK, fail here');
}
}),
catchError((e, c) => throwError(e)) // This line has changed
)
.subscribe(
{
complete: () => console.log('Complete should not be triggered in an error case, but here it is'),
error: (err: any) => {
console.log('Now I have something to do, yay!');
}
});
I've got an Api call that is converted to a promise. My handleError function inside the observable re-throws via throwError. This re-thrown error does not trigger any catch in the outer Promise chain.
callApi() {
return this.http.get(`${this.baseUrl}/someapi`)
.pipe(
map((data: any) => this.extractData(data)),
catchError(error => this.handleError(error))
).toPromise();
handleError(error) {
console.error(error);
return throwError(error || 'Server error');
}
Calling code...
this.someService.callApi()
.then((response) => {
// THIS GETS CALLED AFTER throwError
// do something cool with response
this.someVar = response;
})
.catch((error) => {
// WE NEVER GET TO HERE, even when I force my api to throw an error
console.log(`Custom error message here. error = ${error.message}`);
this.displayErrorGettingToken();
});
Why doesn't the throwError trigger the Promise catch?
You should not use toPromise() when possible.
Use subscribe instead of then.
Also when you catch the error in a pipe it won't be thrown in then because you already caught it, also when you throw the error in a catch error, it won't be emitted into the regular pipe flow of your response.
callApi() {
return this.http.get(`${this.baseUrl}/someapi`);
}
This is totally ok. Http.get() returns a singleton observable stream, which emits only ONE value and then completes. Subscribe to the Observable.
this.someService.callApi()
.subscribe((response) => {
// THIS GETS CALLED always wenn everything is ok
this.someVar = response;
},
(error:HttpErrorResponse) =>{
console.log(`Custom error message here. error ${error.message}`);
this.displayErrorGettingToken();
});
Observable is like an extended version of promise. Use it.
I've written this Effect to handle one call at a time:
#Effect()
indexCollectiveDocuments$ = this.actions$.pipe(
ofType<IndexCollectiveDocuments>(CollectiveIndexingActionTypes.IndexCollectiveDocuments),
mergeMapTo(this.store.select(getIndexingRequest)),
exhaustMap((request: any[], index: number) => {
return zip(...request.map(item => {
this.currentItem = item;
return this.indexingService.indexDocuments(item).pipe(
map((response: any[]) => new IndexCollectiveDocumentsSuccess(response)),
catchError(error => of(new IndexCollectiveDocumentsFailure({ error: error, item: this.currentItem })))
)
}))
})
);
It does dispatch both Success and Failure actions according to the request result.
But when I feed the effect with multiple items(which are the payload of getIndexingRequest) to send requests one after another, the Success and Failure actions are not dispatched accordingly, cancels when one is failed.
How do I modify it so that it works with multiple requests, rather than one?
EDIT: I can see all the requests and their results in the network tab. But only one action is dispatched.
You're using the RxJS operator exhaustMap which will cancel incoming requests when there is already one request running.
To run all requests use either mergeMap (run parallel) or concatMap (run serial).
I have a confusion about switchMap in rxjs Observable:
for example i have next code:
Observable.fromEvent(button, 'click')
.switchMap(() => Observable.fromPromise(fetch('http://return-error.com')))
.subscribe(
(response) => {
console.log(response);
},
(error) => {
console.log(error);
}
);
If I get error from fetch, subscription is interrupted. So is there way to handle it to not create new subscription for any error?
I have even tried to catch error and return Observable, but subscription is interrupted anyway.
upd: how to deal with angular 2 http.get instead of fetch?
It's always more helpful if you make a Bin when asking these questions. I did it for you on this one.
You simply need to swallow the error in the fetch call. E.g.
fetch('bad-url').catch(err => 'Error but keep going')
Here's the demo. Click the document (output) to fire the event.
http://jsbin.com/vavugakere/edit?js,console,output
(You'll need a browser with native Fetch implementation or it'll throw an error)