I am trying to take a rxjs source observable, representing a network connection that pushes me data, and reconnect (by resubscribing to the source observable) if I have not received data within a timeout period. I can certainly write this in a somewhat hacky way, but is there a good way to write this concisely with rxjs?
I ultimately wrote an operator. I think there is a better way to do this, but seeing as how no one else has an idea either, here's the pipeable operator that I wrote:
import { Observable, Subscription } from "rxjs";
export function retryAfterTimeout<T>(timeout: number, allowCompletion = false): (obs: Observable<T>) => Observable<T> {
return source => new Observable<T>(observer => {
let sub: Subscription | undefined;
let timer: number | undefined;
function resetTimer() {
if (timer) clearTimeout(timer);
timer = window.setTimeout(() => resub(), timeout);
}
function resub() {
if (sub) sub.unsubscribe();
sub = source.subscribe({
next(x) {
resetTimer();
observer.next(x);
},
error(err) {
observer.error(err);
},
complete() {
if (allowCompletion)
observer.complete();
else
resub();
}
});
}
resub();
resetTimer();
return () => {
if (sub) sub.unsubscribe();
if (timer) window.clearTimeout(timer);
};
});
}
Related
I am consuming an external API recursively while waiting for the completion of another API call.
The http calls are made by using import {HttpClient} from '#angular/common/http'
I am new to the framework and maybe there is something done wrong in the code but the work-flow is as below:
The first API call is made by this block
initializeCrawling(): void {
this.callCrawlInitializingAPI() // this method is calling the api
.subscribe(
restItems => {
this.groupCrawlingRestResponse = restItems;
console.log(this.groupCrawlingRestResponse);
this.isCrawlActive = false;
}
)
this.fetchResults(); // while callCrawlInitializingAPi call executes, this block here must executed on paralel.
}
Now I am declaring a global
boolean
variable that will became false when this.callCrawlInitializingAPI() execute finish.
Here is the code for the second API call that must be called recursively.
fetchResults(): void {
this.fetchDataApiCall()
.subscribe(
restItems => {
this.groupCrawlingRestResponse = restItems;
console.log(this.groupCrawlingRestResponse);
}
)
}
fetchDataApiCall() {
do {
this.http
.get<any[]>(this.fetchResultsUrl, this.groupCrawlingResultRestResponse)
.pipe(map(data => data));
console.log("Delaying 3000");
} while (this.isCrawlActive);
}
The Goal here is to delay the do - while loop by lets say 1 second.
I Have try the following:
1 - imported {delay} from "rxjs/internal/operators" and use as above;
do {
this.http
.get<any[]>(this.fetchResultsUrl, this.groupCrawlingResultRestResponse)
.pipe(map(data => data));
console.log("Delaying 3000");
delay(3000);
} while (this.isCrawlActive);
2- use setTimeout() function as above:
do {
setTimeout(function(){
this.http
.get<any[]>(this.fetchResultsUrl, this.groupCrawlingResultRestResponse)
.pipe(map(data => data));}, 1000)
} while (this.isCrawlActive)
None of them are working and as far as I understand the do while loop is not delayed, and processes a lot of calls as do while continues.
First of all I want to know how to make it work and second if there is a better way on doing this with Angular since I am new to the framework.
Thank you
UPDATE
My question has a correct answer if anyone will search for this in the future.
The only thing that I had to change was this line of code
clearInterval(intervalHolder.id)
First of all when you are subscribing to a function containing http event, the function must return the stream/http-call
fetchDataApiCall() {
return this.http
.get<any[]>(this.fetchResultsUrl, this.groupCrawlingResultRestResponse)
.pipe(map(data => data));
}
After that if you want to delay the response, you must put the delay operator in the pipe, like that.
fetchDataApiCall() {
return this.http
.get<any[]>(this.fetchResultsUrl, this.groupCrawlingResultRestResponse)
.pipe(map(data => data),
delay(3000));
}
UPDATE
As the comments before the update says there is some trouble with the clear interval, so here is 100% tested solution for the case. The in the first block of code there is the polling logic, pretty much as long the isActive property is true, each 500 ms new request will be called.
The second block of code is the service that simulates the requests.
export class ChildOne {
longReqData;
shortReqData = [];
active = true;
constructor(private requester: RequesterService) {}
loadData() {
this.requester.startLongRequest().subscribe(res => {
this.longReqData = res;
console.log(res);
this.active = false;
});
let interval = Observable.interval(500);
let sub = interval.subscribe(() => {
this.requester.startShortRequest().subscribe(res => {
this.shortReqData.push(res);
console.log(res);
});
if (this.active === false) {
sub.unsubscribe();
}
});
}
}
#Injectable()
export class RequesterService {
private counter = 0;
stop() {
this.subject.next(false);
}
startShortRequest() {
this.counter += 1;
let data = {
delay: 0,
payload: {
json: this.counter
}
};
return this.mockRequest(data);
}
startLongRequest() {
let data = {
delay: 3000,
payload: {
json: "someJSON"
}
};
return this.mockRequest(data);
}
mockRequest(data) {
return Observable.of(data).pipe(delay(data.delay));
}
}
What would be the best way to implement a stoppable list of asynchronous tasks ?
The number of tasks aren't fixed.
I've found a way but I'm wondering if there is a better way to implement such thing. Also the code looks rather dirty, which makes me think that that's probably not the way to go.
The main issues are:
Detect when the end of tasks is requested.
Be able to execute a chain of asynchronous functions, with the possibility of quitting the process at one point, regardless of the remaining tasks.
For issue (2), I found a decent way, which is by using async.waterfall. Sending a value to the error parameter of the callback causes all the processes to stop.
Now comes the dirty part, for (1). To achieve this, each task has to listen for a value change in a object (let's stay, the state of the sequence) in order to detect when quitting the process is required.
So, a prototype of the implementation looks like that:
let sequenceState = new Proxy(
{
stopped: false,
onStop: () => { }
},
{
set: function (target, prop, value) {
if (prop === "stopped") {
target.stopped = value;
if (value)
target.onStop();
} else {
target[prop] = value;
}
}
}
);
function stop() {
sequenceState.stopped = true;
}
function task1(callback) {
console.log("Task 1...");
let to = setTimeout(() => {
console.log("Done after 2000 ms");
callback();
}, 2000);
sequenceState.onStop = () => {
clearTimeout(to);
callback(true);
};
}
function task2(callback) {
console.log("Task 2...");
let to = setTimeout(() => {
console.log("Done after 3000 ms");
callback();
}, 3000);
sequenceState.onStop = () => {
clearTimeout(to);
callback(true);
};
}
async.waterfall(
[
task1,
task2
],
function (err, results) {
console.log("End of tasks");
});
setTimeout(() => {
console.log("Stopped after 2500 ms !");
stop();
}, 2500);
<script src="https://cdn.jsdelivr.net/npm/async#2.6.0/dist/async.min.js"></script>
Does anyone have a better way of implementing this ?
I have been using this pattern:
func myObservable() Observable<boolean> {
...
}
func myFunc() {
myObservable().subscribe((cond: boolean) => {
if (cond) {
// How do I unsubscribe here?
}
});
}
However I can't see any way to unsubscribe thereby maybe creating a memory leak.
The reason I ask is because Angular 2's HTTP client uses the same pattern - although I believe it auto-unsubscribes somehow and I would like to do the same.
You should do something like this:
func myFunc() {
var subscription = myObservable().subscribe((cond: boolean) => {
if (cond) {
// How do I unsubscribe here?
subscription.unsubscribe()
}
});
}
I'm new to Angular 2 so maybe this is trivial - I'm trying to figure out how to cancel an Rx.Observable interval call that happens every 2 seconds. I subscribe to the Rx.Observable with this:
getTrainingStatusUpdates() {
this.trainingService.getLatestTrainingStatus('train/status')
.subscribe(res => {
this.trainingStatus = res;
if (this.trainingStatus == "COMPLETED") {
//Training is complete
//Stop getting updates
}
});
}
This is how my Rx.Observable interval call is handled in my service.ts file (this is called by the function above from a different file):
getLatestTrainingStatus(url: string) {
return Rx.Observable
.interval(2000)
.flatMap(() => this._http.get(url))
.map(<Response>(response) => {
return response.text()
});
}
As you can see in the subscription method, I simply want to stop the interval calls (every 2 seconds) when 'trainingStatus' is 'COMPLETED'.
That's possible with the unsubscribe option.
Every .subscribe() returns a subscription object. This object allows you to unsubscribe to the underlying Observable.
getTrainingStatusUpdates() {
// here we are assigning the subsribe to a local variable
let subscription = this.trainingService.getLatestTrainingStatus('train/status')
.subscribe(res => {
this.trainingStatus = res;
if (this.trainingStatus == "COMPLETED") {
//this will cancel the subscription to the observable
subscription.unsubscribe()
}
});
}
getTrainingStatusUpdates() {
this.trainingService.getLatestTrainingStatus('train/status')
.do(res => this.trainingStatus = res)
.first(res => res == "COMPLETED")
.subscribe(/* Do stuff, if any */);
}
You don't have to unsubscribe / stop the interval. It will be stopped when res == "COMPLETED"
I'm trying to use RxJS for a simple short poll. It needs to make a request once every delay seconds to the location path on the server, ending once one of two conditions are reached: either the callback isComplete(data) returns true or it has tried the server more than maxTries. Here's the basic code:
newShortPoll(path, maxTries, delay, isComplete) {
return Observable.interval(delay)
.take(maxTries)
.flatMap((tryNumber) => http.get(path))
.doWhile((data) => !isComplete(data));
}
However, doWhile doesn't exist in RxJS 5.0, so the condition where it can only try the server maxTries works, thanks to the take() call, but the isComplete condition does not work. How can I make it so the observable will next() values until isComplete returns true, at which point it will next() that value and complete().
I should note that takeWhile() does not work for me here. It does not return the last value, which is actually the most important, since that's when we know it's done.
Thanks!
We can create a utility function to create a second Observable that emits every item that the inner Observable emits; however, we will call the onCompleted function once our condition is met:
function takeUntilInclusive(inner$, predicate) {
return Rx.Observable.create(observer => {
var subscription = inner$.subscribe(item => {
observer.onNext(item);
if (predicate(item)) {
observer.onCompleted();
}
}, observer.onError, observer.onCompleted);
return () => {
subscription.dispose();
}
});
}
And here's a quick snippet using our new utility method:
const inner$ = Rx.Observable.range(0, 4);
const data$ = takeUntilInclusive(inner$, (x) => x > 2);
data$.subscribe(x => console.log(x));
// >> 0
// >> 1
// >> 2
// >> 3
This answer is based off: RX Observable.TakeWhile checks condition BEFORE each element but I need to perform the check after
You can achieve this by using retry and first operators.
// helper observable that can return incomplete/complete data or fail.
var server = Rx.Observable.create(function (observer) {
var x = Math.random();
if(x < 0.1) {
observer.next(true);
} else if (x < 0.5) {
observer.error("error");
} else {
observer.next(false);
}
observer.complete();
return function () {
};
});
function isComplete(data) {
return data;
}
var delay = 1000;
Rx.Observable.interval(delay)
.switchMap(() => {
return server
.do((data) => {
console.log('Server returned ' + data);
}, () => {
console.log('Server threw');
})
.retry(3);
})
.first((data) => isComplete(data))
.subscribe(() => {
console.log('Got completed value');
}, () => {
console.log('Got error');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.1/Rx.min.js"></script>
It's an old question, but I also had to poll an endpoint and arrived at this question. Here's my own doWhile operator I ended up creating:
import { pipe, from } from 'rxjs';
import { switchMap, takeWhile, filter, map } from 'rxjs/operators';
export function doWhile<T>(shouldContinue: (a: T) => boolean) {
return pipe(
switchMap((data: T) => from([
{ data, continue: true },
{ data, continue: shouldContinue(data), exclude: true }
])),
takeWhile(message => message.continue),
filter(message => !message.exclude),
map(message => message.data)
);
}
It's a little weird, but it works for me so far. You could use it with the take like you were trying.
i was googling to find a do while behavior, i found this question. and then i found out that doWhile takes in a second param inclusive boolean. so maybe you can do?:
takeWhile((data) => !isComplete(data), true)