RxJS 5.0 "do while" like mechanism - javascript

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)

Related

RxJS retry only last item, blocking the stream

I'm trying to use RxJS to process a stream of items, and I would like for it to retry any failures, preferentially with a delay (and exponential backoff if possible), but I need to guarantee the ordering so I effectively want to block the stream, and I don't want to reprocess any items that were already processed
So I was trying to play with retryWhen, following this example:
const { interval, timer } = Rx;
const { take, map, retryWhen, delayWhen, tap } = RxOperators;
const source = take(5)(interval(1000));
source.pipe(
map(val => {
if (val >= 3) {
throw val;
}
return val;
}),
retryWhen(errors =>
errors.pipe(
delayWhen(val => timer(1000))
)
)
);
But the stream restarts at the beginning, it doesn't just retry the last one:
Is it possible to achieve what I want? I tried other operators from docs as well, no luck. Would it be kinda against RxJS philosophy somehow?
The retryWhen should be moved to an inner Observable to handle the failed values only and keep the main Observable working.
Try something like the following:
// import { timer, interval, of } from 'rxjs';
// import { concatMap, delayWhen, map, retryWhen, take } from 'rxjs/operators';
const source = interval(1000).pipe(take(5));
source.pipe(
concatMap((value) =>
of(value).pipe(
map((val) => {
if (val >= 3) {
throw val;
}
return val;
}),
retryWhen((errors) => errors.pipe(delayWhen(() => timer(1000))))
)
)
);

How do I ensure I won't replace a content to an old response?

Good day for all,
I am doing a React course and I'd submited the code to the reviewer. He's returned me few comments and there is one comment I'm not being able to solve.
The comment is the following:
Check if (query === this.state.query) to ensure you are not going to replace the contents to an old response
And part of the code is the one below:
updateQuery = (query) => {
this.setState({
query: query
})
this.updateWantedBooks(query);
}
updateWantedBooks = (query) => {
if (query) {
BooksAPI.search(query).then((wantedBooks) => {
if (wantedBooks.error) {
this.setState({ wantedBooks: [] });
} else {
this.setState({ wantedBooks: wantedBooks });
}
})
} else {
this.setState({ wantedBooks: [] });
}
}
Anyone could help me what do am I suppose to do?
Regards.
Code reviewer is right, you don't really want to replace the response if user has entered the very same query.
You have to store somewhere what for user has searched recently:
this.setState({ wantedBooks: [], query });
In case of success response:
this.setState({ wantedBooks, query });
And then check it in case of further searches:
if (query && query !== this.state.query) {
// continue the search only if query is different that current
Instead of relying on an outer member which is open to abuse by other code, you can employ a factory function to more safely trap a member.
As you have discovered, trapping and testing query == this.state.query can be made to work but is arguably not the best solution available.
With a little thought, you can force each call of updateWantedBooks() automatically to reject the previous promise returned by the same function (if it has not already settled), such that any success callbacks chained to the previous promise don't fire its error path is taken.
This can be achieved with a reusable canceller utility that accepts two callbacks and exploits Promise.race(), as follows:
// reusable cancellation factory utility
function canceller(work, successCallback) {
var cancel;
return async function(...args) {
if (cancel) {
cancel(new Error('cancelled')); // cancel previous
}
return Promise.race([
work(...args),
new Promise((_, reject) => { cancel = reject }) // rejectable promise
]).then(successCallback);
};
};
Here's a demo ...
// reusable cancellation factory utility
function canceller(work, successCallback) {
var cancel;
return async function(...args) {
if (cancel) {
cancel(new Error('cancelled')); // cancel previous
}
return Promise.race([
work(...args),
new Promise((_, reject) => { cancel = reject })
]).then(successCallback);
};
};
// delay utility representing an asynchronous process
function delay(ms, val) {
return new Promise(resolve => {
setTimeout(resolve, ms, val);
});
};
function MySpace() {
// establish a canceller method with two callbacks
this.updateWantedBooks = canceller(
// work callback
async (query) => delay(500, query || { 'error': true }), // a contrived piece of work standing in for BooksAPI.search()
// success callback
(wantedBooks => this.setState(wantedBooks)) // this will execute only if work() wins the race against cancellation
);
this.setState = function(val) {
console.log('setState', val);
return val;
};
};
var mySpace = new MySpace();
mySpace.updateWantedBooks({'value':'XXX'}).then(result1 => { console.log('result', result1) }).catch(error => { console.log(error.message) }); // 'cancelled'
mySpace.updateWantedBooks(null).then(result2 => { console.log('result', result2) }).catch(error => { console.log(error.message) }); // 'cancelled'
mySpace.updateWantedBooks({'value':'ZZZ'}).then(result3 => { console.log('result', result3) }).catch(error => { console.log(error.message) }); // {'value':'ZZZ'} (unless something unexpected happened)
Note that canceller() doesn't attempt to abort the asynchronous process it initiates, rather it stymies the success path of the returned promise in favour of the error path.
I think reviewer's point is that response of Search API is asynchronous and result for "query 1" can arrive after user changed his mind and already requested search "query 2". So when response arrive - we need to check if we really interested in it:
updateQuery = query => {
this.setState({
query: query
wantedBooks: []
})
this.updateWantedBooks(query);
}
updateWantedBooks = query => {
if (query) {
BooksAPI.search(query).then((wantedBooks) => {
// if updateQuery("query1) and updateQuery("query2") called in a row
// then response for query1 can arrive after we requested query2
// => for some period of time we'll show incorrect search results
// so adding check if query still the same can help
if (query !== this.state.query) {
// outdated response
return;
} else if (wantedBooks.error) {
// query is okay, but server error in response
this.setState({
wantedBooks: []
})
} else {
// success response to requested query
this.setState({ wantedBooks });
}
})
}
}
Guys I´ve done some tests with your answers, but I realize that somehow the code was behavioring strangely.
So, I've seen in other part of the reviewer comments, a part which I hadn't had seen before do my answer here, the following comment:
Inside 'then' part of the promise check if(query === this.state.query) to ensure you are not going to replace the contents to an old response.
And this "Inside 'then'" has been beating in my brain.
So, I think I've arrived in a satisfatory code; sure, maybe it isn't the definite solution, that's why I want to show here for you and feel free to comment if I'd have to make some improvement. Here below I put the code:
updateQuery = (query) => {
this.setState({
query: query
})
this.updateWantedBooks(query);
}
updateWantedBooks = (query) => {
if (query) {
BooksAPI.search(query).then((wantedBooks) => {
if (wantedBooks.error) {
this.setState({ wantedBooks: [] });
} else if (query !== this.state.query) {
this.setState( { wantedBooks: [] });
} else {
this.setState({ wantedBooks: wantedBooks });
}
})
} else {
this.setState({ wantedBooks: [] });
}
}
Regards

Retry failed API call to another origin in Angular

I want to implement a logic of retrying an API call to another origin if the call to current origin failed.
I want it to be handled in the service layer, for not to implement this logic in each component.
For example, I have such function in endpoint service
getAll(): Observable<IssueListItem[]> {
let endpointUrl = `${this.apiConnectionService.getApiUrl()}api/Issues`;
return this.http.get<IssueListItem[]>(endpointUrl, { headers: this.dataService.requestHeaders })
.pipe(retryOtherApi(this.apiConnectionService),
catchError(error => this.handleError(error)));
}
The consumer of this function looks like:
ngOnInit() {
this.issuesEndpointService.getAll()
.subscribe(_ => this.issues = _);
}
And I whant it know nothing about retry logic.
So, I tried to create an operator 'retryOtherApi' where I switch an origin to another one.
export function retryOtherApi(apiConnectionService: ApiConnectionService) {
const maxRetry = apiConnectionService.apiOriginsCount;
return (src: Observable<any>) => src.pipe(
retryWhen(_ => {
return interval().pipe(
flatMap(count => {
console.log('switch to: ' + apiConnectionService.getApiUrl())
apiConnectionService.switchToOther();
return count === maxRetry ? throwError("Giving up") : of(count);
})
);
})
);
}
Switching works, but unfortunately, the whole getAll function is not called and it retries n times with the same old URL.
So the question is how to implement common retry to other API logic if the current API become unavailable.
If to rephrase the question to the more common case, it becomes like how to recall HTTP endpoint with other params if the current one failed.
public getAll(): Observable<IssueListItem[]> {
let call = () => {
let endpointUrl = `${this.apiConnectionService.getApiUrl()}api/Issues`;
return this.http.get<IssueListItem[]>(endpointUrl, { headers: this.dataService.requestHeaders })
};
return <Observable<IssueListItem[]>>call()
.pipe(
retryOtherApi(this.apiConnectionService, () => call()),
catchError(error => this.handleError(error))
);
}
And here is a retry operator:
export function retryOtherApi(apiConnectionService: ApiConnectionService, recall: () => Observable<any>) {
const maxRetry = apiConnectionService.apiOriginsCount;
let count = 0;
let retryLogic = (src: Observable<any>) => src.pipe(
catchError(e => {
if (e.status === 0) {
apiConnectionService.switchToOther();
console.log('switch to: ' + apiConnectionService.getApiUrl())
return count++ === maxRetry ? throwError(e) : retryLogic(recall());
} else {
return throwError(e);
}
}));
return retryLogic;
}
It works, but:
Now I need to implement logic for other threads not to switch api
simultaneously.
Have some problems with TS typecasting, that is
why I hardcoded type before return.

Delaying recursive HTTP get request inside a do while loop in Angular 6

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));
}
}

Create an array from sync and async values

The code below works just fine. Console outputs an array [1, 2].
const getAsyncValue = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve(1);
}, 1000)
})
}
const getSyncValue = () => {
return Rx.Observable.of(2);
}
const observer = (arrayOfValues) => {
console.log(arrayOfValues);
}
Rx.Observable.of(getPromise(), getSyncValue())
.concatAll()
.toArray()
.subscribe(observer)
I'd like to change function getSyncFunction to the following (because in a real world application this function might not have a reference to RxJs library):
const getSyncValue = () => {
return 2;
}
If I just do that without anything else I get an error:
You provided '2' where a stream was expected
What other changes to the code do I need? Maybe hint me the operator to use.
The problem is not in getSyncValue() but in concatAll() that works with higher-order Observables. When you pass it just 2 it throws the error. Using Rx.Observable.of(2) is correct because it's an Observable that emits a single value 2 which is remitted by concatAll().
I don't know what your code should do but you could do for example:
Rx.Observable.of(getPromise(), getSyncValue())
.map(v => typeof(v) == 'object' ? v : Rx.Observable.of(v))
.concatAll()
...
However, I do recommend to rethink this because I guess you could use some easier approach than this.

Categories

Resources