I would like to repeat an API call which returns a Promise, conditionally using rxjs.
The API method receives an id which will be changed on every call by adding a counter prefix to it. The calls will be repeated until the data met some condition or the counter reach to a specific number X. How it can be done using rxjs?
API method:
fetchData(id):Promise<data>
try 1: fetchData(id)
try 2: fetchData(id_1)
try 3: fetchData(id_2)
CodePudding user response:
IMO, it's better to handle the polling either through Promises or RxJS without mixing them. I'd illustrate using RxJS.
Try the following
- Convert the promise to an observable using the RxJS
from
function. - Use RxJS functions like
timer
orinterval
to regularly emit a value on a fixed interval. - Use a higher order mapping operator like
switchMap
to map from the outer emission to your API call. Refer here for a brief description about different types of higher order mapping operators. - Use two
takeWhile
operators, one for each of your condition respectively, to complete the subscription. - Use
filter
operator to only forward the emissions that pass the condition.
import { from } from 'rxjs';
fetchData(id: any): Observable<any> { // <-- return an observable
return from(apiCall); // <-- use `from` to convert Promise to Observable
}
import { timer } from 'rxjs';
import { filter, switchMap, takeWhile } from 'rxjs/operators';
timer(0, 5000).pipe( // <-- poll every 5 seconds
takeWhile((index: number) => index < 20) // <-- stop polling after 20 attempts
switchMap((index: number) =>
this.someService.apiCall(index 1) // <-- first emission from `timer` is 0
),
takeWhile((response: any) =>
response.someValue === someOtherValue // <-- stop polling when a condition from the response is met
),
filter((response: any) =>
response.someValue === someOtherValue // <-- forward only emissions that pass a condition
)
).subscribe({
next: (response: any) => {
// handle response
},
error: (error: any) => {
// handle error
}
});
CodePudding user response:
You can use concatMap to ensure only one call is tried at a time. range
gives the maximum number of calls because takeWhile
will unsubscribe early (before the range is done) if a condition is/isn't met.
That might look like this:
// the data met some condition
function metCondition(data){
if(data/*something*/){
return true;
} else {
return false
}
}
// the counter reach to a specific number X
const x = 30;
range(0, x).pipe(
concatMap(v => fetchData(`id_${v === 0 ? '' : v}`)),
takeWhile(v => !metCondition(v))
).subscribe(datum => {
/* Do something with your data? */
});
CodePudding user response:
You could try retryWhen:
let counter=0;
const example = of(1).pipe(
switchMap(x => of(counter)), // Replace of() with from(fetchData('id_' counter))
map(val => {
if (val < 5) {
counter ;
// error will be picked up by retryWhen
throw val;
}
return val;
}),
retryWhen(errors =>
errors.pipe(
// log error message
tap(val => console.log(`Response was missing something`)),
)
)
);
It's not ideal as it needs a counter in the outer scope, but until there is a better solution (especially without time based retries) this should work.
CodePudding user response:
I know you've specified using rxjs, however you've also specified that fetchData()
returns a promise
and not an observable
. In this case I would suggest using async
and await
rather than rxjs.
async retryFetch() {
let counter = 0;
while (counter < 20 && !this.data) {
this.data = await this.fetchData(counter);
}
}
You can put whatever you want in the conditional.
Even if your api call returned an observable, I would still suggest wrapping it in a promise and using this very readable solution.
The stackblitz below wraps a standard http.get
with a promise and implements the above function. The promise will randomly return the data or undefined.
https://stackblitz.com/edit/angular-ivy-rflclt?file=src/app/app.component.ts
CodePudding user response:
let count = 0
const timerId = setTimout( () =>{
if(count){
fetchData(`id_${count}`)
}else{
fetchData('id')
}
count = count 1
,60000}) //runs every 60000 milliseconds
const stopTimer = () =>{ //call this to stop timer
clearTimeout(timerId);
}