I have the following observable: messages$: Observable<Message[] | undefined>. Message has 2 fields: id and content, both of which are string.
What I would like to do is to modify messages$ so that a function foo(string) is invoked on the content of each Message.
It doesn't seem difficult at face value but I'm new to observables and unfortunately I got stuck.
I guess solution is simple:
messages$: Observable<Message[] | undefined> = yourSource
.pipe(
map(messages => {
messages.forEach(value => {
value.content = foo(value.content);
});
return messages;
}
)
What you are asking is how can you change your Observable to an observable with sideeffect. You probably don't ever want that (except for simple cases like logging stuff).
Instead what you want to do is subscribe to that Observable and then do your logic in the subscription. That way you're also guaranteed that your logic is only run once (or the number you want) instead of being reliant on something else subscribing to the observable.
messages$.subscribe(({ content }) => { foo(content); });
Be careful of subscription that is not unsubscribed.
Check out this question for a solution to that generic problem:
RXJS - Angular - unsubscribe from Subjects
If i misunderstood your question, and what you really want is an observable that transforms the data, and your foo method is pure (does not modify the inputs or other external data), the solution is different:
const modifiedMessages$ = messages$.pipe(map(({ content }) => foo(content));
Is the takeUntil will manage the subscription of the mergeMap when destroyed$ is emitted in the following code? In other words, is the mergeMap leak a subscription?
combineLatest([
this.selectedCustomerId$.pipe(
-->mergeMap<--(customerId => invoicesService.getInvoices(customerId)
),
this.configuration$
]).pipe(
takeUntil(this.destroyed$)
).subscribe([invoices, configuration] => this.displayInvoices(invoices, configuration));
Thanks!
Yes, there will be no memory leakage.
To understand that you need to understand how pipe works -
Without calling subscribe - there is no subscriptions, doesn't matter what operators you have put it, what pipe does, is it creates a new observable, which, while subscribing to, subscribe to the source observable, and while unsubscribing, it unsubscribe to the source observable.
Even though you have two pipes here - the "un-subscription" will bubble up. i.e. when destroyed$ emits the combine latest subscription will end, than, each of the combineLatest parameters subscription will end, than response of the pipe in which you used the mergeMap will end, which means the return value of the mergeMap will end too.
I hope it wasn't too complicated of an explanation, the short version is that when using pipe everything is connected so when you unsubscribe it unsubscribes all sources.
I want to have multiple subscriptions to react to an observable event, but I want to log the event as well, so I pipe it through a do() operator in which I do the logging.
The problem is, the event gets logged once for each of the subscriptions I create!
I'm getting around this at the moment by creating a Subject and calling next on it from an event callback, which allows me to log the event once and trigger multiple subscriptions as well.
Here is some code that demonstrates the issue: https://stackblitz.com/edit/rxjs-xerurd
I have a feeling I'm missing something, isn't there a more "RxJS" way of doing this?
EDIT:
I'm not asking for a difference between hot & cold observable, in fact I was using a hot observable - the one created by fromEvent() and was wondering why does my presumably hot event source behave like it's cold.
I realize now - after reading about share() - that pipe() "turns" your observable cold i.e. returns a cold one based on the your source (which may be cold, may be hot)
Because Observable sequences are cold by default, each subscription will have a separate set of site effects.
If you want your side effect to be executed only once - you can share subscription by broadcasting a single subscription to multiple subscribers. To do this you can use share, shareReplay, etc.
To better understand how it works, what is "cold" and publish, refer to the RxJS v4 documentation:
4.8 Use the publish operator to share side-effects
EDIT : share() is finally working. Please have a look to the comments below. Thx to #Oles Savluk.
I let my answer below for the records. It may help.
share() and multicasting stuffs did not solve my very similar issue.
Here is how I solved it : https://stackblitz.com/edit/rxjs-dhzisp
const finalSource = new Subject();
fromEvent(button3, "click").pipe(
tap(() => {
console.log("this happens only once")
})
).subscribe(() => finalSource.next())
finalSource.subscribe(
() => console.log("first final subscription")
)
finalSource.subscribe(
() => console.log("second final subscription")
)
finalSource.subscribe(
() => console.log("third final subscription")
)
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.
Can somebody explain me why below code triggers request to server three times? if i were subscribing directly to http.get() i know that its cold observable so it will result callin server 3 times and i need to use .share() to avoid that. but why the same behavior when i am subscribing to subject which is hot.. weird:
let testTrigger = new Subject<any>();
let testTask$ = testTrigger.switchMap(()=> this.restClient.get('onet'));
testTask$.subscribe(console.log.call(console));
testTask$.subscribe(console.log.call(console));
testTask$.subscribe(console.log.call(console));
testTrigger.next(1);
In fact most operators would do the same. i.e. if obs is hot, then obs.op is in general cold. A few operators also returns hot observables (groupBy for example). In the end you need to read the documentation or test to see the nature of the observable you have in your hand.
For more details you can have a look at Hot and Cold observables : are there 'hot' and 'cold' operators?.
Change your code to:
let testTask$ = testTrigger.switchMap(()=> this.restClient.get('onet')).share();
This way all subscriptions will share the same pipe. I guess that when you subscribe you trigger the mapping (hence the http call).