Redux Observable / RxJS: How to create custom observable? - rxjs

I'm trying to do websocket setup in an redux-observable epic, and i'm going with an approach similar to this guy: https://github.com/MichalZalecki/connect-rxjs-to-react/issues/1
However, it looks like my first stab at wiring things up isn't working, even though it looks the same as the guy above:
import 'rxjs';
import Observable from 'rxjs';
import * as scheduleActions from '../ducks/schedule';
export default function connectSocket(action$, store) {
return action$.ofType(scheduleActions.CANCEL_RSVP)
.map(action => {
new Observable(observer => {
// do websocket stuff here
observer.next('message text');
});
})
.map(text => {
console.log("xxxxxxxxxxxxx: ", text);
return scheduleActions.rsvpCancelled(1);
});
};
However, I'm getting a Object is not a constructor error:
=== UPDATE ===
Looks like the suggestion to destructure the { Observable } export worked!
Not the only issue is that text doesn't seem to cross over to the next method...
import 'rxjs';
import { Observable } from 'rxjs';
import * as scheduleActions from '../ducks/schedule';
export default function connectSocket(action$, store) {
return action$.ofType(scheduleActions.CANCEL_RSVP)
.map(action => {
new Observable(observer => {
// do websocket stuff here
observer.next('message text');
});
})
.map(text => {
console.log("xxxxxxxxxxxxx: ", text); // prints undefined
return scheduleActions.rsvpCancelled(1);
});
};

In RxJS v5, the Observable class is available as named export, not the default export.
import { Observable } from 'rxjs';
Importing from regular rxjs will also import all of RxJS (adding all operators to the Observable prototype). This is described in the docs here. If you'd prefer to be more explicit and only import Observable itself you can import it directly at rxjs/Observable:
import { Observable } from 'rxjs/Observable';
Separately, you have a couple issues with the way you're mapping your custom Observable.
First Issue
You're not actually returning it. hehe. You're missing a return statement (or you can remove the curly braces and use arrow function implicit returns).
Second Issue
The regular .map() operator does not do anything special when you return an Observable. If you want the custom Observable to be subscribed to and flattened you'll need to use an operator that does flattening of some kind.
The most common two are mergeMap (aka flatMap) or switchMap.
action$.ofType(scheduleActions.CANCEL_RSVP)
.mergeMap(action => {
return new Observable(observer => {
// do websocket stuff here
observer.next('message text');
});
})
Which operator you need depends on your desired behavior. If you're not yet familiar, you can check out the documentation on the various operators or jump straight to the mergeMap and switchMap docs.
If you're adventurous, RxJS v5 does have WebSocket support out of box you can try with Observable.webSocket(). It's not documented very well, but you could also take a look at the unit tests, and for simple read-only unidirectional streaming it's pretty self explanatory--provide the URL and subscribe. It's actually incredibly powerful, if you can figure out how to use it, that is. Supports bi-directional, multiplex aka complex multiple input/output channels through a single socket. We use it at Netflix for several internal tools with thousands of rps.

You can take a look at Demo. Visit at Create Custom Observable

Related

Rxjs refCount callback to cleanup once every subscribers have unsubscribed?

I've got an observable which is not long lived (http request).
I'm using publishReplay(1) and refCount() so that when there an attempt to access it at the same time, it'll return the same value without triggering the http call again.
But if all the subscriptions are unsubscribed, I need to make some cleanup.
I can't use finalize because:
if I use it before publishReplay then it get closed once the http request is done
if I use it after refCount it'll be run as soon as one observable unsubscribe (instead of when all have unsubscribed)
So basically what I'd like would be to pass a callback to refCount and call that callback when the number of subscriptions reaches 0. But it doesn't work like that. Is there any way to be "warned" when all the subscribers have unsubscribed?
The simplest way I can think of right now would be to create a custom operator that'd pretty much extend refCount to add a callback.
Any better thoughts? I'm pretty sure that there's a better way of doing that.
Thanks!
I am not gonna lie, I was happy to find out I wasn't the only one looking for something like that. There is one another person.
I ended up doing something like that:
import { Observable } from 'rxjs';
export function tapTeardown(teardownLogic: () => void) {
return <T>(source: Observable<T>): Observable<T> =>
new Observable<T>((observer) => {
const subscription = source.subscribe(observer);
return () => {
subscription.unsubscribe();
teardownLogic();
};
});
}
And you use it like:
const augmented = connection.pipe(
tapTeardown(() => /* SOME TEARDOWN LOGIC */),
shareReplay({ bufferSize: 1, refCount: true }),
);
I've tried it and it seems to work correctly.
Here's how it's used:
import { of, timer } from 'rxjs';
import { map, publishReplay, take } from 'rxjs/operators';
import { refCountCb } from './refCountCb';
const source = timer(2000, 10000).pipe(
map(x => `Hello ${x}!`),
publishReplay(1),
refCountCb(() => console.log('MAIN CLOSED'))
);
source.pipe(take(1)).subscribe(x => console.log(x));
source.pipe(take(1)).subscribe(x => console.log(x));
Output:
Hello 0!
Hello 0!
MAIN CLOSED
I've built the custom refCountCb operator based on the source of refCount. It's basically just adding a callback so I won't copy paste the whole code here but it's available on the stackblitz.
Full demo: https://stackblitz.com/edit/rxjs-h7dbfc?file=index.ts
If you have any other idea please share it, I'd be glad to discover different solutions!

observable event filters and timeout

I am very new Rxjs observable and need help with two questions.
I have this piece of code:
const resultPromise = this.service.data
.filter(response => data.Id === 'dataResponse')
.filter((response: dataResponseMessage) => response.Values.Success)
.take(1)
.timeout(timeoutInSeconds)
.map((response: dataResponseMessage) => response.Values.Token)
.toPromise();
I have following basic questions:
1- How can I change .timeout(timeoutInSeconds) to add a message so that I can debug/log later which response it fails? I looked at .timeout syntax in rxjs and didn't see an option to include any message or something.
2-I know .filter((response: dataResponseMessage) => response.Values.Success) will filter to responses with response.Values.Success but is there a syntax where I can do like this for an observable:
const resultPromise = this.service.data
.filter(response => data.Id === 'dataResponse')
.magicSyntax((response: dataResponseMessage) => {
if (response.Values.Success) {
// do something
} else {
// do something else
}
});
Thank you so much in advance and sorry if these are basic/dumb questions.
First question
If you reach timeout the operator will return you an error which can be caught with .catch operator
const resultPromise = this.service.data
.filter(response => data.Id === 'dataResponse')
.filter((response: dataResponseMessage) => response.Values.Success)
.take(1)
.timeout(timeoutInSeconds)
.catch(e=>{
//do your timeout operation here ...
return Observable.Of(e)
})
.map((response: dataResponseMessage) => response.Values.Token)
.toPromise();
Second question simply replace magicSyntax with map or mergemap depends what you want to return from this operation. it is perfectly fine to do if in side the block.
I'm assuming you are using at least Rxjs version 5.5 which introduced pipeable operators. From the docs - these can "...be accessed in rxjs/operators (notice the pluralized "operators"). These are meant to be a better approach for pulling in just the operators you need than the "patch" operators found in rxjs/add/operator/*."
If you aren't using pipeable operators, instead of passing the operators to pipe() like I did below, you can chain them using the dot notation you use in your example.
I suggest referring to learnrxjs.io for some additional info about the operators in RxJS, paired with examples.
The RxJS team has also created a BETA documentation reference.
Explanation
I assumed the first filter is receiving the response and filtering by response.Id instead of data.Id. If that wasn't a typo, you can keep the filter the same.
I added an extra line between the operators for presentation only.
mergeMap is an operator that takes a function that returns an Observable, which it will automatically subscribe to. I'm returning of() here, which creates an Observable that just emits the value provided to it.
catch was renamed to catchError in RxJS 5.5, and pipeable operators were also added, which add support for the .pipe() operator.
If you don't want to do anything besides logging the error, you can return empty(), which will immediately call complete() on the source Observable without emitting anything. EMPTY is preferred if you are using version 6.
Optional: Instead of using filter() and then take(1), you could use the first() operator, which returns a boolean, just like filter(), and unsubscribes from the source Observable after it returns true once.
import {EMPTY, of} from 'rxjs';
import {catchError, filter, take, mergeMap, timeout} from 'rxjs/operators';
const resultPromise = service.data.pipe(
// I assumed you meant response.Id, instead of data.Id
filter((response: dataResponseMessage) => response.Id === 'dataResponse'),
take(1),
// mergeMap accepts a value emitted from the source Observable, and expects an Observable to be returned, which it subscribes to
mergeMap((response: dataResponseMessage) => {
if (response.Values.Success) {
return of('Success!!');
}
return of('Not Success');
}),
timeout(timeoutInMilliseconds),
// catch was renamed to catchError in version 5.5.0
catchError((error) => {
console.log(error);
return EMPTY; // The 'complete' handler will be called. This is a static property on Observable
// return empty(); might be what you need, depending on version.
})
).toPromise();
1- you can use .do() to console.log your response.
.filter(..).do(response => console.log(response))
2- you can use .mergeMap()

I would like to have an observable behaving like a promise

var promise = http.get().toPromise(); gives a promise which executes once.
So no matter how much times promise.then() is called the http request is done only once.
How can I achieve the same with an observable?: var observable = http.get().??? so that obversable.subscribe() only performs the http request one time?
you can use the following rxjs operator 'take' like this
import the operators
import { take } from 'rxjs/operators';
and use it like this
this.http.get(`${this.url}/${agentId}/intents/${intentId}`)
.pipe(take(1),
catchError(error => Observable.throw(error)));
but this will only emit a single value once, the latest value, not stop the Observable from calling the request if you call this function again.
The following code did it:
import { shareReplay } from 'rxjs/operators';
this.http.get("xxx").pipe(shareReplay(1));

RxJS: Is there an no-op observable?

I have an action that will then trigger an ajax request.
If the action fails for some reason, I want to do nothing. Instead of creating a blank action that just returns the previous state, is there a no-op function I can execute?
export default function fetchMeetups(action$) {
return action$.ofType(statusActions.START_APP)
.mergeMap(action =>
ajax.getJSON(`${config.API_BASE_URL}/api/v1/meetups`)
.map(meetups => calendarActions.meetupsReceived(meetups))
)
.catch(error => Observable.noop())
};
I already have the meetups saved from the last time the app was open (using redux-persist), so if the api request fails I just want it to do nothing.
Is this possible?
I found this from Rxjs but I have no clue how to use it: https://xgrommx.github.io/rx-book/content/helpers/noop.html
Heads up: that link to xgrommx references RxJS v4, not v5 or v6. noop is also just a function that does nothing--not an Observable which emits nothing, which is what I believe you're looking for.
That said, I would highly discourage against swallowing errors completely like this. It can make debugging this and other things very very hard later. I would at least log the error message out.
v5 comes with Observable.empty() or import { empty } from 'rxjs/observable/empty'; which produces an Observable that will emit nothing and just immediately complete.
However, there are some other subtleties you probably will run into next. If you let the ajax error propagate up to the outer operator chain, outside of the mergeMap, your Epic will not longer be listening for future actions! Instead, you'll want to catch errors as early as possible, in this case by placing the catch inside the mergeMap. We often call this "isolating our observer chains"
export default function fetchMeetups(action$) {
return action$.ofType(statusActions.START_APP)
.mergeMap(action =>
ajax.getJSON(`${config.API_BASE_URL}/api/v1/meetups`)
.map(meetups => calendarActions.meetupsReceived(meetups))
.catch(e => {
console.error(e);
return Observable.empty();
})
);
};
Now, whenever the ajax (or the map operation) errors, we're catching that error before it propagates out and instead switching to our empty Observable which will complete immediately so the inner chain is now "done" but our Epic will continue to listen for future actions.
UPDATE:
In v6 empty() is imported from the root import { empty } from 'rxjs'; or it is also available as a singleton import { EMPTY } from 'rxjs';, which can be used as-is, you don't call it like you would empty(). It can be reused because Observables are lazy and act like a factory anyway so empty() was redundant.
import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
// etc
source$.pipe(
catchError(e => {
console.error(e);
return EMPTY; // it's not a function, use it as-is.
})
);
As rxjs also accepts arrays, you can simple provide an empty array when you don't want to emit anything
...
.catch(error => return [];)

RxJs subscribe works but map does not

I have an ngrx store like this:
export default compose(storeLogger(), combineReducers) ({
auth: authReducer,
users: userReducer
});
In a service I try to do the following:
import 'rxjs/add/operator/do';
#Injectable()
export class ApiService {
constructor(private _http: Http, private _store: Store<AppState>, private _updates$: StateUpdates<AppState>) {
_store.select<Auth>('auth').do(_ => {console.log("token:" +_.token)});
}
No operator works except subscribe. Why?
If you are asking in general why this occurs then here is an explanation by Andre Stalz on his blog.
http://staltz.com/how-to-debug-rxjs-code.html
Because Observables are lazy until you subscribe, a subscription triggers the operator chain to execute. If you have the console.log inside a do and no subscription, the console.log will not happen at all.
So basically this is the typical behavior of operators.
In your example you have attached a "do" operator. With no subscription to the observable that the "do" operator returns it will not fire. Most operators won't fire until there is at least one subscription on the observable the operator returns. Map is one of those.
http://jsbin.com/bosobuj/edit?html,js,console,output
var source = new Rx.BehaviorSubject(3);
source.do(x=>console.log(x));
var source2 = new Rx.BehaviorSubject(5);
source2.do(x=>console.log(x)).subscribe(x=> x);
the output is 5 because only source2 "do" is executed.

Resources