Rxjs - Tap isn't triggered by subscribe in concatMap() - rxjs

I have a Subject that maps an observable, I subscribe to the observable in the concatMap but it doesn't trigger tap() from the subscription.
this.streamA$ = this.streamService.getStream(1)
.pipe(
tap(data => console.log('stream data:', data))
);
Subject
this.images$ = this.queue.pipe(concatMap((event: Observable<string>) => {
// when an event arrives here it is still wrapped in observable
// subscribe isn't triggering tap()
event.subscribe(data => {
//console.log('inner observable subscription:', data);
});
// observable goes to the image$ observable, it is unwrapped by: image$ | async in template
return event;
}));
Service function
getStream(time: number): Observable<string> {
let timer$ = timer(2000);
console.log('get stream');
const observable = new Observable<string>(observer => {
timer$.pipe(
map(() => {
observer.next('http response 1');
observer.complete();
})
).subscribe();
});
return observable;
}
Update:
event.subscribe(data => {
//console.log('inner observable subscription:', data);
});
Without console.log included in subscribe this is the output:
stream data: http response 1
With console.log, prints these 3 lines at the same time:
stream data: http response 1
inner observable subscription: http response 1
stream data: http response 1

When you have an Observable inside a concatMap, you don't need to subscribe. Could you try something along these lines?
this.images$ = this.queue.pipe(
concatMap(event => event), // I assume the event here is something like this.streamA$
tap(data => // do stuff with data)
);

Related

How to get rid of 'return new Promise' and not to lose data?

In this case, there is nothing easier to wait until the data is received, and then resolve the Promise:
// Using Promise resolve/reject
module.exports = () => {
return new Promise(async (resolve, reject) => {
let doc = await Document();
doc.on('data', async (data) => {
resolve(data);
});
})
}
But what do I do in this case?
// Using async/await
module.exports = async () => {
let doc = await Document();
doc.on('data', (data) => {
// ???
});
}
You still need the new Promise, you just should use it as the operand of an await inside the async function, not using an async function as the executor:
module.exports = async () => {
const doc = await Document();
return new Promise(resolve, reject) => {
doc.on('data', resolve);
});
};
However, I would recommend to use once instead of on so that the event handler is removed after the first occurrence of the event - the promise can be resolve only once anyway. Also if you have node v11.13.0 or higher you can just use the events.once method so that you don't have to build the promise yourself - and it also handles error events correctly:
const { once } = require('events');
module.exports = async () => {
const doc = await Document();
return once(doc, 'data');
};

Merging a new rxjs observable into existing subscribed observable

I have an rxjs observable which is already subscribed to:
const o = new rxjs.Subject();
o.subscribe( (v) => console.log("value: " + v);
Due to some unrelated event, e.g. a user clicking on a button, I need to go and fetch data from the server, and put that data into the existing observable o. Using axios, I have something like this:
const aPromise = axios.get("someurl...")
I then do the following:
const secondObservable = rxjs.of(aPromise)
.subscribe( (response) => o.next(response.data) )
My question is whether there is a better way to put the response data into the existing already subscribed to observable, perhaps using operators?
If you know that you need data from your original stream and might get updates from a button click — then we'll need to create a stream of button clicks, e.g.
const btnClick$ = new rxjs.Subject();
onClick(){
this.btnClick$.next(void 0);
}
Then we'll turn this btnClick$ stream into a http-get request stream, using switchMap:
const request$ = btnClick$.pipe(
// each click will be turned into a http-get
switchMap(() => rxjs.of(axios.get("someurl...")))
)
And we're ready to consume results from both streams, original and a stream of possible data updates from server on button click:
// we'll take results both from original stream
// and our new possible source of data
merge(original$, request$).subscribe(data => {
console.log(data);
})
Heres a little app using mocks:
const { fromEvent, of, merge } = rxjs;
const { map, delay, switchMap } = rxjs.operators;
// create a stream of btn clicks
const theButton = document.getElementById('the-btn');
const btnClicks$ = fromEvent(theButton, 'click');
console.log('Processing...');
// clicks turned into http-gets
const request$ = btnClicks$.pipe(
switchMap(() => makeRequest())
);
// original source of data
const original$ = of('Please, click the button!').pipe(delay(500));
merge(original$, request$)
.subscribe(data => {
console.log(data);
});
// mock for requests
function makeRequest(){
return of(Date.now()).pipe(delay(500))
}
<script src="https://unpkg.com/rxjs#6.4.0/bundles/rxjs.umd.min.js"></script>
<button
id="the-btn"
>Click me!</button>
Hope this helps
You can just pass the result of the call directly to your observable.
const axios = {
get: url => Promise.resolve('some value')
};
const o = new rxjs.Subject();
o.subscribe(v => { console.log("value: " + v); });
axios.get('url').then(data => { o.next(data); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>

how to unsubscribe from an observable returned from Observable.create in rxjs 5.1

I have this code:
async download(fileToUpload: UploadedFileMetaData): Promise<Observable<DownloadEvent>> {
const url = await this.getDownloadUrl(fileToUpload);
let xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
return Observable.create((observer) => {
console.log(observer);
xhr.open('GET', url);
xhr.send();
xhr.addEventListener('progress', (progress) => {
let percentCompleted;
That returns an ovservable.
I then use it like this:
const downloadSubscription = await this.blobStorageService.download(file);
downloadSubscription.subscribe((event) => // do stuff
Multiples of these might be created.
How do I unsubscribe?
You can store the subscribe() method return value which is a Subscriber object in a variable and call the unsubscribe() method when you want to unsubscribe.
const downloadSub = downloadSubscription.subscribe((event) => {});
downloadSub.unsubscribe();
In the end I went with this:
const destroy$ = new Subject<boolean>();
downloadSubscription.takeUntil(destroy$).subscribe(
// main body
},
(downloadEvent: FailureDownloadEvent) => {
// error
destroy$.next(true);
destroy$.unsubscribe();
},
() => {
// cleanup
destroy$.next(true);
destroy$.unsubscribe();
}
);
}

Rxjs Subscribing until another event happens

I am new to Rxjs and am trying to implement the following workflow in it:
User clicks on a menu item that triggers an HTTP request
Before the response has arrived, the user clicks on a second request
The subscription to the first request is ended and a subscription to the second request is started
// The code below sits inside the onClick event of my menu
var callAction = function(someParameters) {
return Rx.Observable.create(function(observer) {
var subscribed = true;
myHttpApi.someActionCall(someParameters).then(
(data: any) => {
if (subscribed) {
// Send data to the client
observer.next(data);
// Immediately complete the sequence
observer.complete();
}
}).catch((err: any) => {
if (subscribed) {
// Inform the client that an error occurred.
observer.error(ex);
}
}
);
return function () {
subscribed = false;
}
});
};
The observer is further defined below:
var observer = {
// onNext in RxJS 4
next: function (data) {
// Do what you need to do in the interface
},
// onError in RxJS 4
error: function (err) {
// Handle the error in the interface
},
// onComplete in RxJS 4
complete: function () {
//console.log("The asynchronous operation has completed.");
}
};
let subscription = callAction(somParameters).subscribe(observer);
How do I now go about implementing #3, whereby the subscription to the first request is ended and a subscription to the new request (in this example, the same block of code is executed for different menu options and therefore different requests based on the parameters) is started?
Breaking up the steps into discrete functions,
// Inner observable, calls the API
const callAction$ = function(someParameters) {
return Observable.fromPromise(
myHttpApi.someActionCall(someParameters)
)
}
// Outer observable, controls the click chain
const click$ = new Subject();
click$.switchMap(clickParams => {
return callAction$(clickParams)
})
.subscribe(
result => console.log('Result: ', result),
err => console.log('Error: ', err.message)
)
// Handler function, called from menu
const handleClick = function(clickParams) {
click$.next(clickParams)
}
Working example CodePen

How to make polling observable detect unfullfilled promise

This observable polls getPromise() function every second. After getPromise() function returns 3 promises it stops resolving them. How do I detect that getPromise() function hasn't resolve/rejected any promise for the past, let's say, 2 seconds, and call onError handler. I've tried making it work with timeout operator to no avail. Any ideas?
Rx.Observable.interval(1000)
.switchMap(() => Rx.Observable.fromPromise(getPromise()))
.subscribe(onValue, onError);
function onValue(value){
console.log('value: ', value);
}
function onError(error){
console.log('error: ', error);
}
var getPromise = (function(){
var counter = 3;
return function(){
return new Promise(function(resolve, reject){
if(counter > 0) resolve(1);
counter--;
})
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.3.0/Rx.js"></script>
You can use the race operator that subscribes only to the first Observable that emits.
You said you want to call onError handler after 2 of inactivity. This contradicts with using switchMap which automatically unsubscribes when a new Observable is returned from its callback. So you might want to use exhaustMap instead. Also when you emit an error notification the chain unsubscribes and you'll never receive any other value. This means that you shouldn't emit the timeout as an error or use also the retry operator to automatically resubscribe (but this really depends on what you're trying to achieve).
This is you updated example that is just using the race() operator.
Rx.Observable.interval(1000)
.switchMap(() =>
Rx.Observable.race(
Rx.Observable.fromPromise(getPromise()),
Rx.Observable.timer(0, 1000).mapTo(42)
)
)
.subscribe(onValue, onError);
function onValue(value){
console.log('value: ', value);
}
function onError(error){
console.log('error: ', error);
}
var getPromise = (function(){
var counter = 3;
return function(){
return new Promise(function(resolve, reject){
if(counter > 0) resolve(1);
counter--;
})
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.3.0/Rx.js"></script>
Edit: To send a single error notification after 2 seconds of inactivity.
Rx.Observable.interval(1000)
.switchMap(() => Rx.Observable.fromPromise(getPromise()))
.timeout(2000)
.subscribe(onValue, onError);
function onValue(value){
console.log('value: ', value);
}
function onError(error){
console.log('error: ', error);
}
var getPromise = (function(){
var counter = 3;
return function(){
return new Promise(function(resolve, reject){
if(counter > 0) resolve(1);
counter--;
})
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.2.0/Rx.js"></script>
There's really a bug in 5.3.0 not directly in the timeout() operator but in scheduling async actions. https://github.com/ReactiveX/rxjs/pull/2580
Without the timeout() operator:
Rx.Observable.interval(1000)
.switchMap(() =>
Rx.Observable.race(
Rx.Observable.fromPromise(getPromise()),
Rx.Observable.timer(0, 2000).map(function(_) {
throw new Error('timeout');
})
)
)
.subscribe(onValue, onError);

Resources