How can I create an observable like the one returned by defer using Observable.create? - rxjs

I'm trying to create an observable with behavior similar to what is returned by defer, but using create method. So I try:
const obs = Observable.create(function(observer) {
from(fetch('https://jsonplaceholder.typicode.com/todos/1').then(console.log('fetch done'))).subscribe(observer)
})
setTimeout(()=>obs.subscribe((resp)=>console.log(resp.statusText)), 5000)
But when I run it in node It just prints "fetch done" (on subscription, after 5 seconds as expected) but waits forever there.
When I wrap the from(..) in setImmediate() or setTimeout(,0), then sometimes it prints both messages ("Fetch done", "OK") and exits and sometimes it only prints "fetch done" and waits forever.
code:
const obs = Observable.create(function(observer) {
setTimeout(()=>from(fetch('https://jsonplaceholder.typicode.com/todos/1').then(console.log('fetch done'))).subscribe(observer), 0)
})
setTimeout(()=>obs.subscribe((resp)=>console.log(resp.statusText)), 5000)
Why this happens? What am I doing wrong?

First thing is that Observable.create is deprecated and you should use just new Observable():
When creating Observables like this you have access to observer object where you can call next() and complete() so in your case it would be like this:
new Observable(observer => {
fetch('https://jsonplaceholder.typicode.com/todos/1').then(response => {
observer.next(response);
observer.complete();
});
});
I think this should work as well:
new Observable(observer => {
const sub = from(fetch('https://jsonplaceholder.typicode.com/todos/1')).subscribe(observer);
// Return tear-down function so you can abort request.
return () => sub.unsubscribe();
});
Obviously, this is over complicated and if you just want to wrap a Promise with Observable you can use just from().

Promise.then takes a callback function, and you need to return the argument to chain it, so it becomes then(result => { console.log('fetch done', result); return result; })

Related

How to rewrite this code using rxjs observables instead async/await?

I need that createUser function returns Observable<UserEntity> but in this function I also have to make 2 queries to DB and check if this user exists. The code below uses async/await and looks pretty good and clean. But the problem is that I use rxjs everywhere in this project and would like to write it somehow using rxjs. Can it be as clean as now but with Observables?
async create(user: CreateUserDTO): Promise<UserEntity> {
const userByEmail = await this.getUserByEmail();
const userByLogin = await this.getUserByLogin();
if (userByLogin || userByEmail)
// retrun error here
return await this.createUser(user);
}
I am using RxJs 6.5
forkJoin will emit result when both async functions getUserByEmail & getUserByLogin complete their execution
If getUserByEmail & getUserByLogin returns Promise,for that using from to convert a promise into an observable
mergeMap to subscribe the inner observable.In our case createUser returns observable
create(user: CreateUserDTO): Observable < UserEntity > {
//If getUserByEmail & getUserByLogin returs Promise
const getUserByEmail$ = from(this.getUserByEmail());
const getUserByLogin$ = from(this.getUserByLogin());
//If Both returns Observable
//const getUserByEmail$ = this.getUserByEmail();
//const getUserByLogin$ = this.getUserByLogin();
return forkJoin({
userByEmail: this.getUserByEmail(),
userByLogin: this.getUserByLogin(),
}).pipe(
tap((res) => {
if (res.userByEmail || res.userByLogin) {
throw 'User exists!';
}
}),
mergeMap(() => {
return from(this.createUser(user));
//If createUser returns Observable,then
//return this.createUser(user);
})
);
}
Assuming that this.getUserByEmail(), this.getUserByLogin() and this.createUser(user) return Promises, the code could look like this
create(user: CreateUserDTO): Observable<UserEntity> {
// with the rxjs from function we turn a Promise into an Observable
const userByEmail$ = from(this.getUserByEmail());
const userByLogin$ = from(this.getUserByLogin());
// with forkjoin we create an Observable which notifies when all the
// Observables which have been passed in as parameters notify
return forkJoin([userByEmail$, userByLogin$]).pipe(
// with concatMap you wait for the upstream Observable (i.e. the
// Observable created by forkJoin) to notify and complete, and then
// you return the next Observable in the chain, which is, in this case,
// the Observable which (when subscribed) creates the user
concatMap(([userByLogin, userByEmail]) =>
if (userByLogin || userByEmail) {
// throw error here
}
return from(this.createUser(user))
})
)
}
Otherwise, if this.getUserByEmail(), this.getUserByLogin() and this.createUser(user) return Observables you do not need to use the from rxjs function and the code would be slightly simpler, like this
create(user: CreateUserDTO): Observable<UserEntity> {
return forkJoin([this.getUserByEmail(), this.getUserByLogin()]).pipe(
concatMap(([userByLogin, userByEmail]) =>
if (userByLogin || userByEmail) {
// throw error here
}
return from(this.createUser(user))
})
)
}

how to unsubscribe a RXJS subscription inside the subscribe method?

I have some javascript:
this.mySubscription = someObservable.subscribe((obs: any) => {
this.mySubscription.unsubscribe();
this.mySubscription = undefined;
}
on execution, the console logs the error ERROR TypeError: Cannot read property 'unsubscribe' of undefined.
I wonder why I can not unsubscribe inside the subscribe lambda function. Is there a correct way to do so? I have read a bit about using dummy-subjects and completing them or using takeUntil/takeWhile and other pipe operators workArounds.
What is a correct way/workaround to unsubscribe a subscription inside the subscription's subscribe-function?
I am currently using a dummy subscription like so:
mySubscription: BehaviorSubject<any> = new BehaviorSubject<any>(undefined);
// when I do the subscription:
dummySubscription: BehaviorSubject<any> = new BehaviourSubject<any>(this.mySubscription.getValue());
this.mySubscription = someObservable.subscribe((obs: any) => {
// any work...
dummySubscription.next(obs);
dummySubscription.complete();
dummySubscription = undefined;
}, error => {
dummySubscription.error(error);
});
dummySubscription.subscribe((obs: any) => {
// here the actual work to do when mySubscription emits a value, before it should have been unsubscribed upon
}, err => {
// if errors need be
});
You shouldn't try to unsubscribe in the subscribe function.
You can unsubscribe with operators like take, takeWhile or takeUntil.
take
Use take(n) to unsubscribe after someObservable emits n times.
someObservable.pipe(
take(1)
).subscribe(value => console.log(value));
takeWhile
Use takeWhile to unsubscribe when an emitted value fails a condition.
someObservable.pipe(
takeWhile(value => valueIsSave(value))
).subscribe(value => console.log(value));
valueIsSave(value): boolean {
// return true if the subscription should continue
// return false if you want to unsubscribe on that value
}
takeUntil
Use takeUntil(obs$) to unsubscribe when the observable obs$ emits.
const terminate = new Subject();
someObservable.pipe(
takeUntil(terminate)
).subscribe(value => console.log(value));
unsub() {
terminate.next() // trigger unsubscribe
}
If you make your stream asynchronous, what you're doing should work. For example, this will not work:
const sub = from([1,2,3,4,5,6,7,8,9,10]).subscribe(val => {
console.log(val);
if(val > 5) sub.unsubscribe();
});
but this will work:
const sub2 = from([1,2,3,4,5,6,7,8,9,10]).pipe(
delay(0)
).subscribe(val => {
console.log(val);
if(val > 5) sub2.unsubscribe();
});
Because the JS event loop is fairly predictable (blocks of code are always run to completion), If any part of your stream is asynchronous, then you can be sure that your subscription will be defined before your lambda callback is invoked.
Should you do this?
Probably not. If your code relies on the internal (otherwise hidden) machinations of your language/compiler/interpreter/etc, you've created brittle code and/or code that is hard to maintain. The next developer looking at my code is going to be confused as to why there's a delay(0) - that looks like it shouldn't do anything.
Notice that in subscribe(), your lambda has access to its closure as well as the current stream variable. The takeWhile() operator has access to the same closure and the same stream variables.
from([1,2,3,4,5,6,7,8,9,10]).pipe(
takeWhile(val => {
// add custom logic
return val <= 5;
})
).subscribe(val => {
console.log(val);
});
takeWhile() can to anything that sub = subscribe(... sub.unsubscibe() ... ), and has the added benefit of not requiring you to manage a subscription object and being easier to read/maintain.
Inspired by another answer here and especially this article, https://medium.com/#benlesh/rxjs-dont-unsubscribe-6753ed4fda87, I'd like to suggest takeUntil() with following example:
...
let stop$: Subject<any> = new Subject<any>(); // This is the one which will stop the observable ( unsubscribe a like mechanism )
obs$
.pipe(
takeUntil(stop$)
)
.subscribe(res => {
if ( res.something === true ) {
// This next to lines will cause the subscribe to stop
stop$.next();
stop$.complete();
}
});
...
And I'd like to quote sentence RxJS: Don’t Unsubscribe from those article title mentioned above :).

Repeat or restart an observable (interval) after its completed

Here is what I am trying to do - step #3 is what I am struggling with.
Set custom interval
trigger an http call (subscribe to it)
Once interval is finished, I want to repeat the above steps indefinitely (until a condition is met)
Steps #1 & #2 are working
setRenewalInterval() {
...
return this.timerSub = interval(CUSTOM_INTERVAL * 1000)
.pipe(
take(1),
map(x => {
if (this.isLoggedIn && !tokenHelper.isTokenExpired()) {
console.log("<Requesting Token Renew>")
this.getNewToken().subscribe(
x => {
console.log("<Token renewed successfully>");
},
err => {
console.log("<Token failed to renew>");
this.setLogOut();
},
() => console.log('<renewal observable completed>')
);
}
else {
console.log("<Not Logged in or Token expired; logging out>");
this.setLogOut();
}
})
)
.subscribe();
}
#3 repeat the above steps after the subscription is completed. Either call setRenewalInterval() or preferably do it inside the setRenewalInterval().
I have looked into rxjs repeat() and expand() but I can't get them to work with the above code. Any help is greatly appreciated, especially a testable/runnable code e.g. jsfiddle.
You're really close, interval(...) is already creating an emission indefinitely however take(1) is causing the entire observable to complete after the first emission.
If you remove the take(1) your code inside the map will be called at your interval forever.
map is a useful function when you want to take values emit in an observable and convert it to a new value synchronously, similar to how Array.map() works. Because you're working with observables inside this map we can use something like mergeMap() which will handle the subscription and piping through of values automatically.
setRenewalInterval() {
// ...
return this.timerSub = interval(CUSTOM_INTERVAL * 1000)
.pipe(
mergeMap(() => {
if (this.isLoggedIn && !tokenHelper.isTokenExpired()) {
return this.getNewToken();
} else {
return this.setLogOut();
}
})
)
.subscribe(x => {
// x is the result of every call to getNewToken() or setLogOut()
});
}

Testing Observables with jest

How can I test Observables with Jest?
I have an Observable that fires ~every second, and I want to test that the 1st event is correctly fired, before jest times out.
const myObservable = timer(0, 1000); // Example here
it('should fire', () => {
const event = myObservable.subscribe(data => {
expect(data).toBe(0);
});
});
This test passes, but it also passes if I replace with toBe('anything'), so I guess I am doing something wrong.
I tried using expect.assertions(1), but it seems to be only working with Promises.
There are some good examples in the Jest documentation about passing in an argument for the test. This argument can be called to signal a passing test or you can call fail on it to fail the test, or it can timeout and fail.
https://jestjs.io/docs/en/asynchronous.html
https://alligator.io/testing/asynchronous-testing-jest/
Examples
Notice I set the timeout to 1500ms
const myObservable = timer(0, 1000); // Example here
it('should fire', done => {
myObservable.subscribe(data => {
done();
});
}, 1500); // Give 1500ms until it fails
Another way to see if it fails using setTimeout
const myObservable = timer(0, 1000); // Example here
it('should fire', done => {
myObservable.subscribe(data => {
done();
});
// Fail after 1500ms
setTimeout(() => { done.fail(); }, 1500);
}, timeToFail);
My preferred way to test observables, without fake timers and timeouts, is to async, await and use resolves or rejects on an expected converted promise.
it('should do the job', async () => {
await expect(myObservable
.pipe(first())
.toPromise())
.resolves.toEqual(yourExpectation);
});
Update:
In Rxjs 7 and onwards, you can use lastValueFrom or firstValueFrom for the promise convertion
it('should do the job', async () => {
await expect(lastValueFrom(myObservable))
.resolves.toEqual(yourExpectation);
});
test('Test name', (done) => {
service.getAsyncData().subscribe((asyncData)=>{
expect(asyncData).toBeDefined();
done();
})
});
})
the correct way to test any RXJS observable (Jest or no) is to the use the TestScheduler in rxjs/testing:
e.g.:
import { TestScheduler } from 'rxjs/testing';
import { throttleTime } from 'rxjs/operators';
const testScheduler = new TestScheduler((actual, expected) => {
// asserting the two objects are equal - required
// for TestScheduler assertions to work via your test framework
// e.g. using chai.
expect(actual).deep.equal(expected);
});
// This test runs synchronously.
it('generates the stream correctly', () => {
testScheduler.run((helpers) => {
const { cold, time, expectObservable, expectSubscriptions } = helpers;
const e1 = cold(' -a--b--c---|');
const e1subs = ' ^----------!';
const t = time(' ---| '); // t = 3
const expected = '-a-----c---|';
expectObservable(e1.pipe(throttleTime(t))).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});
});
From the RXJS marble testing testing docs.
Trying to convert observables, etc. into promises works fine if you have a simple observable. As soon as things become more complicated you are going to struggle without using marble diagrams and the correct testing library.
There are 2 approaches mentioned above
Taking argument done in our test and call it when we have tested.
Convert our observable to promise using firstValueFrom(myObs) or lastValueFrom(myObs). and use async await with them...
If we have multiple observables to test then we have to nest the observables in our test as we can call done() only once. In that case async await approach can come handy.
In this example when we call filter Customer all three observables emits values so we have to test all of them.
it('Filter Customers based on Producers- Valid Case Promise way ',async()=>{
service.filterCustomers('Producer-1');
await expect(firstValueFrom(service.customers$)).resolves.toEqual(['Customer-1']);
await firstValueFrom(service.customers$).then((customers:string[])=>{
expect(customers).toEqual(['Customer-1']);
expect(customers.length).toBe(1);
})
await expect(firstValueFrom(service.products$)).resolves.toEqual([]);
await expect(firstValueFrom(service.types$)).resolves.toEqual([]);
}).
Here's an Angular approach using fakeAsync
Suppose we have a FooService with an Observable closed$ that emit every time we call the dismiss() method of the service.
#Injectable()
export class FooService {
private closeSubject$ = new Subject<void>();
public close$ = this.closeSubject$.asObservable();
public dismiss() {
this.closeSubject$.next();
}
}
Then we can test the close$ emission like this
describe('FooService', () => {
let fooService: FooService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [FooService]
});
fooService= TestBed.inject(FooService);
});
it('should emit a close event upon calling dismiss()', fakeAsync(() => {
const callbackSpy = jest.fn();
fooService.close$.subscribe(() => {
callbackSpy();
});
fooService.dismiss();
tick();
expect(callbackSpy).toHaveBeenCalledTimes(1);
}));
});

Redux Observable: How to return an action from a callback?

I'm using the WebRTC library which has a very specific API. The peerConnection.setRemoteDescription method's 2nd argument is supposed to be a callback for when it finishes setting the remote description:
This is one of my wrapper functions for my WebRTC class:
export function setRemoteSdp(peerConnection, sdp, callback) {
if (!sdp) return;
return peerConnection.setRemoteDescription(
new RTCSessionDescription(sdp),
callback, // <-------------
);
}
And this is a sketch of what I want to do:
function receivedSdp(action$, store) {
return action$.ofType(VideoStream.RECEIVED_SDP)
.mergeMap(action => {
const {peerConnection} = store.getState().videoStreams;
const {sdp} = action.payload;
return WebRTC.setRemoteSdp(peerConnection, sdp, () => {
return myReducer.myAction(); // <------ return action as the callback
})
})
};
This doesn't work since I'm not returning an Observable. Is there a way to do this?
P.S. this is the WebRTC API: https://github.com/oney/react-native-webrtc/blob/master/RTCPeerConnection.js#L176
martin's answer is correct about using Observable.create or new Observable--same thing (except it's not clear to me why you need the mergeAll() since the mergeMap will flatten?)
As a bonus, you could also use Observable.bindCallback for this.
// bindCallback is a factory factory, it creates a function that
// when called with any arguments will return an Observable that
// wraps setRemoteSdp, handling the callback portion for you.
// I'm using setRemoteSdp.bind(WebRTC) because I don't know
// if setRemoteSdp requires its calling context to be WebRTC
// so it's "just in case". It might not be needed.
const setRemoteSdpObservable = Observable.bindCallback(WebRTC.setRemoteSdp.bind(WebRTC));
setRemoteSdpObservable(peerConnection, sdp)
.subscribe(d => console.log(d));
Usage inside your epic would be something like this
// observables are lazy, so defining this outside of our epic
// is totally cool--it only sets up the factory
const setRemoteSdpObservable = Observable.bindCallback(WebRTC.setRemoteSdp.bind(WebRTC));
function receivedSdp(action$, store) {
return action$.ofType(VideoStream.RECEIVED_SDP)
.mergeMap(action => {
const {peerConnection} = store.getState().videoStreams;
const {sdp} = action.payload;
return setRemoteSdpObservable(peerConnection)
.map(result => myReducer.myAction());
})
};
You could use this to create Observable wrappers for all the WebRTC apis.
So the problem is that setRemoteSdp doesn't return an Observable while myReducer.myAction() does and that's the Observable you want to merge?
You can use Observable.create and wrap the WebRTC.setRemoteSdp call:
.mergeMap(action => {
return Observable.create(observer => {
WebRTC.setRemoteSdp(peerConnection, sdp, () => {
observer.next(myReducer.myAction());
observer.complete();
})
});
}
.mergeAll()
The Observable.create returns an Observable that emits another Observable from myReducer.myAction(). Now I have in fact so-called higher-order that I want to flatten using mergeAll() (concatAll would work as well).

Resources