Suppose I have two completely independent pieces of code in two completely unrelated classes that subscribe to the same Observable in a service class.
class MyService {
private readonly subject = new Subject<any>();
public observe(): Observable<any> {
return this.subject.pipe();
}
}
class A {
constructor(private readonly service: MyService) {
service.observe().subscribe( async (value) => {
await this.awaitAnOperation(value);
console.log('Class A subscription complete', value);
});
}
}
class B {
constructor(private readonly service: MyService) {
service.observe().subscribe( (value) => console.log('Class B subscription complete', value));
}
}
The issue that I am now facing is that when the service emits an event, the log of class B will come before A, even though A subscribed first. What I need is that all methods are ran and finished before going to the next one. I know if A were to be synchronously than my question would be solved, but A does need to run an async operation AND Bcan only log after A has logged.
A and B are completely unaware of eachother and should be as well. In e.g. C# we can run an async method synchrnonously by using GetAwaiter().Wait(); and is not considered a bad practice since when it needs to run on the main thread. An equivalent TS/JS option would be nice.
EDIT
A subscribes before B. It is simply the chronological order of subscribing that should also execute. I know this is by default emitted in that sequence, but the fact remains that running a subscription method on a different thread would continue the main thread to the next subscription. This is what I need to avoid somehow.
I had a similar issue that I solved with an operator I called forkConcat. Instead of subscribing multiple times, I made multiple operators and chained them so that source$.pipe(operatorA) would happen and complete before source$.pipe(operatorB) started, and that would complete before source$.pipe(operatorC) started, and all three completed before dealing with the next value from source$.
My code looked like this...
source$.pipe(
forkConcat(
concat,
operatorA,
operatorB,
operatorC
) )
where forkConcat is defined as
import { merge, Observable, of } from 'rxjs';
import { concatMap, Operator } from 'rxjs/operators';
/*
Creates an operator that will fork several operators from the same input, and not proceed until all those things are done.
First Argument:
If those things should be done in turn, send concat as the first argument.
If each operator should be done in parallel, use merge (or undefined) as the first argument.
To return an array of each operators' final values per value received by forkConcat, use forkJoin.
You could also use combineLatest, etc.
All other arguments are operators.
*/
type Combiner<T> = (...array$: ObservableInput<T>[]) => Observable<T>;
export function forkConcat<T,R>(combiner: Combiner<T> = merge, ...arrayOperators: Operator<T, R>[]) {
return concatMap<T,R>((x) => {
try {
const x$ = of(x);
const o = arrayOperators
.filter(op => !!op) // ignore falsy arguments
.map(op => x$.pipe(op));
return o.length ? combiner(...o) : x$;
} catch (e) {
throw new ForkConcatError(x, e, combiner, arrayOperators);
}
});
}
class ForkConcatError<T> extends Error {
constructor(
readonly receivedValue: T,
readonly innerError: Error,
readonly combiner: Combiner<T>,
readonly arrayOperators: Operator<T, R>[]
) {
super(innerError.message);
}
}
It worked. But I've also got to tell you...
I threw it away
I slowly began to realize that the need for forkConcat was a sign that I should be doing things differently. I haven't seen your code but when you say they shouldn't know about each other yet one affects the other, I highly suspect you should consider the same. If, instead of global/shared variables, you had global/shared subjects and observables where the one that emitted to B was...
source$.pipe(
concatMap(x => concat(
of(x).pipe(
operatorA,
ignoreElwments()
),
of(x) // B should receive the same value A did.
)
)
... then you wouldn't have this issue and your code would be cleaner.
In my case, I went a different route. I made a State interface, and then instead of passing source values through my operators, I passed {source: Source, state: State} objects. This way, there was no longer any global variables at all! Every operator could be a pure function (or pure function plus side effects) using only the combined value/state pairs emitted into them. State errors were much harder to make! To use this tactic, start thinking of your A and B as operators (that don't know about each other) instead of subscribers (that don't know about each other) and your future self will likely be happier.
But just in case I'm wrong about that advice, you now have forkConcat.
You can do that by combining the two Observables using switchMap rxjs operator. it will be guarantied that the second Observable B will not started unless the first one A is done.
Here a good example to this scenario in the section addressed "Combining Observables in series":
https://blog.danieleghidoli.it/2016/10/22/http-rxjs-observables-angular/
Related
Language: Typescript 3.5x
Environment: Angular 8.x, RxJS 6.x
I'm trying to figure out how to get rid of a chain of promises that looks (simplified) like this:
ClassA.methodA1 is called
-methodA1 does an if/then check and if true, calls ClassB.methodB1
--methodB1 calls a different function and combines the results with a parameter passed to it, and ultimately calls ClassC.methodC1
---methodC1 does some validation and then calls ClassD.methodD1 if valid
----methodD1 makes an async call (to a DB) and waits for it to finish before returning back the value it received (an ID).
When methodA1 receives the id, it calls ClassA.methodA2 and passes that Id in as a parameter
The method called by methodD1 is outside my control, and returns a Promise. I can't change that. However, I need methodA1 to wait until methodD1 is complete, and it receives the ID - each intervening method simply passes that return value back up to its caller.
Even this simplified version smells. NONE of the intervening methods (methodB1 or methodC1, and there are actually 2 more in the real code) need to be async. The only reason they are currently is that I need everything to wait until methodD1 finishes before continuing. Each intervening method simply returns the Promise returned from the method it calls.
I refactored with async/await and while there is less code, it is still a series of methods that simply pass the return value they receive from a method call back up the chain, and it's still a bunch of unnecessary async/await on the intervening methods.
I refactored to RxJS Subjects and having methodA1 subscribe and then methodD1 call .next(id) when it is complete but this doesn't really feel any better. I potentially have several dozen similar flows across my app and it feels like going the Subject route is a lot of extra plumbing overhead and keeping straight which subscription response belongs to which subscription instance is going to be problematic.
My gut tells me that the Subject route is the right approach, but I was wondering if there was something I was missing that makes it cleaner. Is there some built-in mechanism to tag subscription responses to a given subscription instance so that the subscription code only processes the response to ITS call and then unsubscribes?
The only other approach I can think of, which is just plain UGLY is to pass my ClassA instance all the way down to ClassD and have ClassD call methodA2 when it receives the id from the DB. But this is even worse.
Is there something I'm missing? If so, what? Any guidance or a pattern reference is appreciated.
The only other approach I can think of is to pass my ClassA instance all the way down to ClassD and have ClassD call methodA2 when it receives the id from the DB. But this is even worse.
That's essentially a callback (with an instance receiving a method call, instead of a plain function getting called), is generally considered worse than simply returning promises.
Notice there is nothing wrong with your original approach, if only component D does calls to the database (which are asynchronous) and all the other modules depend on D because they do things that use the database, then the other modules naturally become asynchronous as well. The async (promise-returning) call being in a tail position seems like a coincidence then, and might change if the components are going to do multiple db calls in sequence.
However, there is another approach that you missed. It is applicable when all of your functions just do some combination/translation/validation of parameters, and in the end pass them into a different method. Instead of having B call C itself, it could simply return the validated parameters to A, and then let A call C directly. This will make B and C completely synchronous and unit-testable, and only A calls the asynchronous method of D.
Thanks for the input. While the chain of promises is a legitimate approach, it just felt wonky, harder to test and reason about. YMMV.
I ended up going a slightly modified route with RxJS Subjects. The difference from what I had before is that the Subjects, instead of being permanent and hard-coded, are dynamically created as needed and stored in a Map. Once they are no longer needed, they are deleted. By giving each a distinct name, I don't need to verify which response "belongs" to which subscription - it is all keyed off of the distinct name as the key of the map.
The only thing I don't like about this approach is having to pass the distinct name all the way down the chain, but I can live with that. Everything flows in one direction - "down" the chain - which is easier for me to reason about.
Here is the psuedo-code version of what I ended up with (all error-checking and class instantiation removed for brevity):
export class ClassA {
methodA1(param1){
if(someCondition){
const theKey = this.generateUniqueKey();
DispatcherInstance.subjects.set(theKey, new Subject<number>());
const sub = DispatcherInstance.get(theKey).subscribe( (dbId: number) => {
sub.unsubscribe();
DispatcherInstance.subjects.delete(theKey);
this.doNextStepWithId(dbId);
}
classBInstance.methodB1(param1, theKey);
}
}
}
export class ClassB {
methodB1(param1, theKey): void {
const result = this.methodB2();
classCInstance.methodC1(param1, result, theKey);
}
}
export class ClassC {
methodC1(param1, result, theKey): void {
if(this.validate(param1, result)){
ClassDInstance.methodD1(param1, theKey);
}
}
}
export class ClassD {
methodD1(param1, theKey: string): void {
db.someFunc(param1).then( (dbId: number) => {
DispatcherInstance.subjects.get(theKey).next(dbId);
});
}
}
export class Dispatcher {
const subjects: Map<string, Subject<number>> = new Map<string, Subject<number>>()
}
I've got a service that I use to share data between 2 components. That part works flawlessly, but now I need to call a method of component A, when something triggers on the service (and pass a value to that component). How can I do this? I read on older questions that this is a wrong approach but since Im a noob I dont know what to search for a solution.
Do I need to use observables?
I think Joseph's idea is the way to go.
Here's how I'd implement it:
class FooService {
private _newEvents = new Subject();
newEvents$ = this._newEvents.asObservable();
addNewEvent (ev) {
this._newEvents.next(e);
}
}
// Allow `A` class to communicate with `B` class
class A {
addEvent (ev) {
this.fooService.addNewEvent(ev);
}
}
class B {
private subscription: Subscription;
ngOnInit () {
this.subscription = this.fooService.newEvents$
.subscribe(e => {})
}
ngOnDestroy () {
this.subscription.unsubscribe();
}
}
Note that if your B class subscribes to multiple observables, you should unsubscribe from them using, among other solutions, takeUntil.
Observables / Subjects are one way. You would have one Subject in the service, and would use .next(value) on it to exchange values. Each component which is interested in the value may subscribe to that subject.
Example: (taken from RxJS docs
//your Service
import { Subject } from 'rxjs';
const subject = new Subject<number>();
//Component A (and others as well)
service.subject.subscribe({
next: (num) => console.log(num)
});
//this should work as well with prettier syntax:
service.subject.subscribe(sum =>
console.log(num)
);
//Component B
service.subject.next(7) //passing number 7 to Component A
Whenever you create a subscription, make sure to always unsubscribe! Else you might end up with stacks of subscriptions, which will all get triggered simultaneously in the very same component.
From personal experience, I found it more helpful to outsource any functions and variables that could be considered as global into a dedicated service, if possible. If you directly read the variables of a service from your components (and modify them if necessary), you'll have the same effect. That works as long as you keep a proper service structure. Some examples of dedicated services with global use are:
Translations (TranslationService)
Rights Management (PermissionService)
I have two observables which I want to combine and in subscribe use either both arguments or only one. I tried .ForkJoin, .merge, .concat but could not achieve the behaviour I'm looking for.
Example:
obs1: Observable<int>;
obs2: Observable<Boolean>;
save(): Observable<any> {
return obs1.concat(obs2);
}
Then when using this function:
service.save().subscribe((first, second) => {
console.log(first); // int e.g. 1000
console.log(second); // Boolean, e.g. true
});
or
service.save().subscribe((first) => {
console.log(first); // int e.g. 1000
});
Is there a possibility to get exactly that behaviour?
Hope someone can help!
EDIT:
In my specific use case obs1<int> and obs2<bool> are two different post requests: obs1<int> is the actual save function and obs2<bool> checks if an other service is running.
The value of obs1<int> is needed to reload the page once the request is completed and the value of obs2<bool> is needed to display a message if the service is running - independant of obs1<int>.
So if obs2<bool> emits before obs1<int>, that's not a problem, the message gets display before reload. But if obs1<int> emits before obs2<bool>, the page gets reloaded and the message may not be displayed anymore.
I'm telling this because with the given answers there are different behaviours whether the values get emitted before or after onComplete of the other observable and this can impact the use case.
There are several operators that accomplish this:
CombineLatest
This operator will combine the latest values emitted by both observables, as shown in the marble diagram:
obs1: Observable<int>;
obs2: Observable<Boolean>;
save(): Observable<any> {
return combineLatest(obs1, obs2);
}
save().subscribe((val1, val2) => {
// logic
});
Zip
The Zip operator will wait for both observables to emit values before emitting one.
obs1: Observable<int>;
obs2: Observable<Boolean>;
save(): Observable<any> {
return zip(obs1, obs2);
}
save().subscribe((vals) => {
// Note Vals = [val1, val2]
// Logic
});
Or if you want to use destructuring with the array
save().subscribe(([val1, val2]) => {
// Logic
});
WithLatestFrom
The WithLatestFrom emits the combination of the last values emitted by the observables, note this operator skips any values that do not have a corresponding value from the other observable.
save: obs1.pipe(withLatestFrom(secondSource))
save().subscribe(([val1, val2]) => {
// Logic
});
You can use forkJoin for this purpose. Call them parallely and then if either of them is present then do something.
let numberSource = Rx.Observable.of(100);
let booleanSource = Rx.Observable.of(true);
Rx.Observable.forkJoin(
numberSource,
booleanSource
).subscribe( ([numberResp, booleanResp]) => {
if (numberResp) {
console.log(numberResp);
// do something
} else if (booleanResp) {
console.log(booleanResp);
// do something
}
});
You may use the zip static method instead of concat operator.
save(): Observable<any> {
return zip(obs1, obs2);
}
Then you should be able to do like the following:
service.save().subscribe((x) => {
console.log(x[0]); // int e.g. 1000
console.log(x[1]); // Boolean, e.g. true
});
The exact operator to use depends on the specific details of what you are trying to solve.
A valid option is to use combineLatest - Docs:
obs1$: Observable<int>;
obs2$: Observable<Boolean>;
combined$ = combineLatest(obs1$, obs2$);
combined$.subscribe(([obs1, obs2]) => {
console.log(obs1);
console.log(obs2);
})
Concat emits two events through the stream, one after the other has completed, this is not what you're after.
Merge will emit both events in the same manner, but in the order that they actually end up completing, also not what you're after.
What you want is the value of both items in the same stream event. forkJoin and zip and combineLatest will do this, where you're getting tripped up is that they all emit an array of the values that you're not accessing properly in subscribe.
zip emits every time all items zipped together emit, in sequence, so if observable 1 emits 1,2,3, and observable two emits 4,5; the emissions from zip will be [1,4], [2,5].
combineLatest will emit everytime either emits so you'll get soemthing like [1,4],[2,4],[2,5],[3,5] (depending on the exact emission order).
finally forkJoin only emits one time, once every item inside it has actually completed,a and then completes itself. This is likely what you want more than anything since you seem to be "saving". if either of those example streams don't complete, forkJoin will never emit, but if they both complete after their final value, forkjoin will only give one emission: [2,5]. I prefer this as it is the "safest" operation in that it guarantees all streams are completing properly and not creating memory leaks. And usually when "saving", you only expect one emission, so it is more explicit as well. When ever you see forkJoin, you know you're dealing with a single emission stream.
I would do it like this, personally:
obs1: Observable<int>;
obs2: Observable<Boolean>;
save(): Observable<any> {
return forkJoin(obs1, obs2);
}
service.save().subscribe(([first, second]) => {
console.log(first); // int e.g. 1000
console.log(second); // Boolean, e.g. true
});
Typescript provides syntax like this to access the items in an array of a known length, but there is no way to truly create multiple arguments in a subscribe success function, as it's interface only accepts a single argument.
Hey I am trying to figure out a way to handle error and api call inside an redux epic, I have checked at this doc:
https://redux-observable.js.org/docs/recipes/ErrorHandling.html
I don't have any error, but nothing happens the code seems looping
/**
* Request the JSON guide to the API
* and then dispatch requestGuideFulfilled & requestGameTask actions
*
* #param action$
*/
export function requestGuide(action$) {
return action$.ofType(REQUEST_GUIDE)
.mergeMap(({id}) => fetchJsonp(`${API_URL}/guide/${id}/jsonp`)
.catch(error => requestGuideFailed(error))
)
.mergeMap(response => response.json())
.mergeMap(json => requestGuideFulfilled(json))
.map(json => requestGameTask(json))
}
export function manageRequestGuideError(action$) {
return action$.ofType(REQUEST_GUIDE_FAILED)
.subscribe(({error}) => {
console.log('Error',error)
})
}
Any Idea ? Thank you !
[UPDATE]: I have an error even on the fetching:
You provided an invalid object where a stream was expected. You can
provide an Observable, Promise, Array, or Iterable.
There are a number of issues, so I'll try and elaborate as best I can. To be frank, RxJS is not easy. I would encourage you to spend some solid time learning the fundamentals before using redux-observable, unless of course you're just experimenting in your free time for fun and you like pain lol.
It's also critical to not bring in things like redux-observable unless you really need complex side effect management. Somewhat unfortunately, the docs currently only have simple examples, but redux-observable is really intended to make complex stuff like multiplex websockets, elaborate time-based sequencing, etc much easier at the expense of needing to know RxJS really well. So I guess I'm saying is, if you do need redux, make sure you need redux-observable too or could get away with redux-thunk. It might seem funny that one of the makers of redux-observable sorta talks people out of using it, but I just see a crazy number of people using things like redux-observable/redux-saga for things that simply don't justify the complexity they bring. You know your needs best though, so don't take this as doctrine or be unreasonably discouraged <3
None of the code in this answer has been tested, so it may need minor corrections.
You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
This error is likely caused by .mergeMap(json => requestGuideFulfilled(json)). It looks like requestGuideFulfilled is an action creator, but the source isn't included here so I can't be sure. mergeMap aka flatMap expects you to return another stream (usually an Observable), so an action POJO would need to be wrapped in an Observable, like Observable.of(requestGuideFulfilled(json)) but in this case using mergeMap is unnecessary. It could have just been a regular map().
export function manageRequestGuideError(action$) {
return action$.ofType(REQUEST_GUIDE_FAILED)
.subscribe(({error}) => {
console.log('Error',error)
})
}
In redux-observable all Epics must return an Observable. This Epic is returning a subscription observable (the return value of subscribe()). This actually does produce an error, however due to a bug in RxJS it's been silently swallowed.
Instead you can use do and ignoreElements to create an Observable that listens for that action, logs that property out, then ignores it (never emitting anything itself). So it's "read only" basically.
export function manageRequestGuideError(action$) {
return action$.ofType(REQUEST_GUIDE_FAILED)
.do(({error}) => {
console.log('Error',error)
})
.ignoreElements();
}
The next and biggest issue is where you've placed your catch. It's important to learn about how using RxJS means we are chaining Observables together--"operators" basically take a source and return another Observable which will lazily apply some operation on data piped through them. This is very similar to functional programming with Arrays (e.g. arr.map().filter()) with two major differences: Observables are lazy and they have a time-dimension.
How operators work
So consider this Observable chain:
Observable.of(1, 2, 3)
.map(num => num.toString())
.filter(str => str !== '2');
.subscribe(value => console.log(value));
We create an Observable that, when subscribed to, will synchronously emit 1, then 2, then 3.
We apply the map operator to that source which creates a new Observable that, when subscribed to, will itself subscribe to source we applied it to: our Observable of 1, 2, 3.
We then apply the filter operatorto the Observable returned bymap. As you might have guessed,filterreturns a new Observable that, when subscribed to, will itself subscribe to the source we applied it to: our Observable of strings we mapped. Because thatmap` Observable itself was applied to a source, it too will then subscribe to its source, pulling in the first number and kicking off the map -> filter operation.
It may be helpful to store those intermediate Observables as variables, to demystify things a bit.
const source1: Observable<number> = Observable.of(1, 2, 3);
const source2: Observable<string> = source1.map(num => num.toString());
const result: Observable<string> = source2.filter(str => str !== '2');
Inner Observables
When we use operators like mergeMap, switchMap, concatMap, we are saying we want to map each value to another "inner" Observable who's values will be either merged, switched to, or queued (concat) after the previous inner Observable. These have different important differences, but there's lots of resources on them if you're unfamiliar.
In your case we're using mergeMap, which also has an alias of flatMap, which is the more widely known term used in functional programming. mergeMap will provide each value to your projection function and concurrently subscribe to the Observable you return for each. The values of each of those inner Observables are merged together.
In functional programming they more often call this flattening, instead of merging. So it may again be helpful to first consider this merging/flattening in the context of Arrays
Array.prototype.map
[1, 3, 5].map(value => {
return [value, value + 1];
});
// [[1, 2], [3, 4], [5, 6]] Array<Array<number>>
This resulted in an array of arrays of numbers Array<Array<number>>, but what if we instead wanted a single, flattened array? Enter flatMap.
Array.prototype.flatMap (stage 2 TC39 proposal)
[1, 3, 5].flatMap(value => {
return [value, value + 1];
});
// [1, 2, 3, 4, 5, 6] Array<number>
JavaScript arrays do yet officially have flatMap, but it's a stage 2 TC39 proposal as of this writing. It follows the same semantics as the typical flatMap: for each item in the array map it to another array provided by your projection function, then flatten each of those into a single new array.
With Observables, it's pretty much the same except again they are lazy and have a time dimension:
Observable.of(1, 3, 5).map(value => {
return Observable.of(value, value + 1);
});
// Observable.of(1, 2)..Observable.of(3, 4)..Observable.of(5, 6) | Observable<Observable<number>>
We mapped each number into their own Observable of two numbers. So a higher-order Observable Observable<Observable<number>> and probably not what we wanted in most cases.
Observable.of(1, 3, 5).flatMap(value => {
return Observable.of(value, value + 1);
});
// 1..2..3..4..5..6 | Observable<number>
Now we just have a stream of all the numbers. Perfect!
Error Handling
Putting together our understanding of operating chaining and Observable flattening, we come to error handling. Hopefully that primer makes this next part easier to grok.
If an error is thrown in any one of our chained Observables it will propagate through the chain in the same fashion as values do, but in its own "channel" basically. So we if we have an Observable chain a -> b -> c and an error occurs in a, it will be sent to b then c. When each Observable receives the error it can either handle it in some way, or choose to pass it along to whatever is subscribing to it. When it does, that subscription terminates and no longer listens for future messages from its source.
Most operators just pass along errors (while terminating), so if you aren't using a special error handling operator like catch the error propagates until it reaches your observer--the one you yourself passed to .subscribe(next, error, complete). If you provided that error handler, it's called, if not, it's rethrown as a normal JavaScript exception.
To finally get to your code, let's start with the end; what I think you actually want:
function getGuide(id) {
const promise = fetchJsonp(`${API_URL}/guide/${id}/jsonp`)
.then(res => res.json());
return Observable.from(promise);
}
export function requestGuide(action$) {
return action$.ofType(REQUEST_GUIDE)
.mergeMap(({id}) =>
getGuide(id)
.mergeMap(json => Observable.of(
requestGuideFulfilled(json),
requestGameTask(json)
))
.catch(error => Observable.of(
requestGuideFailed(error)
))
)
}
Now let's break it down.
Promise vs. Observable
First thing you'll see is that I abstracted out your fetchJsonp into the getGuide function. You could just as well put this code inside the epic, but having it separate will make it easier for you to mock it if you decide to test.
As quickly as possible I wrap that Promise in an Observable. Mostly because if we're choosing to use RxJS we should go all-in, especially to prevent confusion later. e.g. both Promise and Observable instances have catch methods so it's easy to cause bugs if you start mixing the two.
Ideally we'd use Observables instead of Promises entirely, as Promises cannot be cancelled (so you cannot cancel the actual AJAX request + JSON parsing itself), although if you wrap it in an Observable and unsubscribe before the promise resolves, the Observable will correctly just ignore what the promise later resolves or rejects.
Emitting multiple actions?
It's not 100% clear, but it appeared you intended to emit two actions in response to successfully getting back the JSON. Your previous code actually maps the JSON to the requestGuideFulfilled() action, but then the next operator maps that action to requestGameTask() (which doesn't receive the JSON, it receives the requestGuideFulfilled() action). Remember above, about how operators are chains of Observables, the values flow through them.
To solve this, we need to think think "in streams". Our getGuide() Observable will emit a single value, the JSON. Given that single (1) value we want to map it to more than one other values, in this case two actions. So we want to transform one-to-many. We need to use one of mergeMap, switchMap, or concatMap then. In this case, since our getGuide() will never emit more than one item all three of these operators will have the same result, but it's critical to understand them cause it often does matter so keep that in mind! Let's just use mergeMap in this case.
.mergeMap(json => Observable.of(
requestGuideFulfilled(json),
requestGameTask(json)
))
Observable.of supports an arbitrary number of arguments, and will emit each of them sequentially.
catching errors
Because our Observable chains are...well..chains hehe the values are piped between them. As we learned above, because of this where you place error handling in these chains is important. This is actually not very different between error handling with traditional exceptions, or even promises, but Promises do not have "operators", so people don't usually run into this confusion.
The catch operator is the most common, and it's pretty similar to the catch Promises have except you must return an Observable of the value you want, not the value itself. Observable.of is common here since most often we just want to emit one or more items sequentially.
.catch(error => Observable.of(
requestGuideFailed(error)
))
Whenever a error is emitted by the source we apply this operator to, it will catch it and instead emit requestGuideFailed(error) and then complete().
Because it emits an action on error, any operators we apply to the result of this .catch() **could also be operating on the value our catch emits.
getJsonSomehow()
.catch(error => Observable.of(
someErrorAction(error)
))
.map(json => {
// might be the JSON, but also might be the
// someErrorAction() action!
return someSuccessAction();
})
Although not unique to redux-observable (since redux-observable is mostly just a tiny library and a convention, using RxJS) you'll often see Epics follow a similar pattern.
Listens for an particular action
Then merges or switches that action into an inner Observable that performs a side effect
When that side effects is successful we map it to an success action
In case it errors we place a catch inside our mergeMap/switchMap but most often at the end of the inner chain, so that any actions we emit aren't transformed on accident.
You'll hopefully recognize that general pattern from the redux-observable docs:
function exampleEpic(action$) {
return action$.ofType(EXAMPLE)
.mergeMap(action =>
getExample(action.id)
.map(resp => exampleSuccess(resp))
.catch(resp => Observable.of(
exampleFailure(resp)
))
);
}
Applying that knowledge to our previous work:
getGuide(id)
.mergeMap(json => Observable.of(
requestGuideFulfilled(json),
requestGameTask(json)
))
.catch(error => Observable.of(
requestGuideFailed(error)
))
And that's about it, I think.
PHEW! Sorry that was so long-winded. It's entirely possible you knew some or all of this, so forgive me if I'm preaching the choir! I started writing something short, but kept adding clarification, after clarification. lol.
If you're struggling, definitely make sure using RxJS and redux-observable (or any complex middleware) is a neccesary complexity for your app.
I found the rxJS docs define them as
What is a Subject? An RxJS Subject is a special type of Observable that allows values to be multicasted to many Observers. While plain Observables are unicast (each subscribed Observer owns an independent execution of the Observable), Subjects are multicast.
and it goes on to give examples but I'm looking for a basic ELI5 explanation. From my understanding is it helps handle and define items in a sequence. Is that correct?
I think it would be most helpful to me and others to see a simple function with and without defining an rxJS Subject to understand why it's important?
Thanks!
The easiest way to understand it is to think of a Subject as both a producer and a consumer. It's like an open channel where someone can send a message on one end, and any subscribers will receive it on the other end.
+---------------
Sender | => => => => Subscriber
-----------------------+ +-----------
Message => => => => => => => => => => => Subscriber
-----------------------+ +-----------
| => => => => Subscriber
+---------------
In code terms say you have a service with a subject
class MessageService {
private _messages = new Subject<Message>();
get messages: Observable<Message> {
return this._messages.asObservable();
}
sendMessage(message: Message) {
this._messages.next(message);
}
}
Note the messages getter returning the Subject as an Observable. This is not required. The Subject is already an observable, and anybody could subscribe directly to the Subject. But I think the asObservable pattern is used as a way to limit what users can do with it, i.e. so users only use it to subscribe to, and not emit to. We save the emitting for the sendMessage method.
Now with this service in place, we can inject it into different components, and this can be a way for two (or more) arbitrary components to communicate (or just receive arbitrary event notifications).
class ComponentOne {
constructor(private messages: MessageService) {}
onClick() {
this.messages.sendMessage(new Message(..));
}
}
class ComponentTwo {
constructor(private messages: MessageService) {}
ngOnInit() {
this.messages.messages.subscribe((message: Message) => {
this.message = message;
});
}
}
Angular's own EventEmitter is actually a Subject. When we subscribe to the EventEmitter, we are subscribing to a Subject, and when we emit on the EventEmitter, we are sending a message through the Subject for all subscribers.
See also:
Subject vs BehaviorSubject vs ReplaySubject in Angular
Subjects are useful when the code you're in is the one that is actually originating the observable data. You can easily let your consumers subscribe to the Subject and then call the next() function to push data into the pipeline.
If, however, you are getting data from other source and are just passing it along (perhaps transforming it first), then you most likely want to use one of the creation operators shown here, such as Rx.Observable.fromEvent like so:
var clicks = Rx.Observable.fromEvent(document, 'click');
clicks.subscribe(x => console.log(x));
This allow you to stay in the functional paradigm, whereas using a Subject, while it has its uses, is considered by some to be a smell that you're trying to force imperative code into a declarative framework.
Here is a great answer that explains the difference in the two paradigms.
If you want the most simple explanation ...
Observables are usually the result of something. The result of an http call, and whatever you do with a pipe returns an observable.
But what is the source of those things? Ever wondered how you hook your user events into the whole rxjs thing? The main feature of subjects is that you can call the next() method on them.
When doing reactive programming, the first step is usually to make a list of possible subject you will have.
For instance: lets say we have to make a todo-list app.
We will probably have a couple of variables in our component:
public deleteItem$ = Subject<TodoItem> = new Subject();
public addItem$ = Subject<TodoItem> = new Subject();
public saveList$ = Subject<TodoItem[]> = new Subject();
and in our applicatiuon we will hook these up like this:
<button (click)="deleteItem$.next(item)">Delete</button>
Using rxjs, we will use operators like merge/combineLatest/withLatestFrom to handle these subjects and define our application logic.
I'll see if I can find the time to make a small example.
You can find a study of the semantics of subjects here.
All answered I see are correct. I'll just add that the term subject comes from the observer pattern (cf. https://en.wikipedia.org/wiki/Observer_pattern). As such a subject is sort of a relay, it receives something on one end, and emit it on any of its ends (subscriptions).