Reputation: 8804
I am trying to use retry
with delay
function, I expect function will call after 1000ms delay, but it doesnot, what can be error here?
look at console output, it is same time 16:22:48.
I expect there 16:22:48, 16:22:59 ...
canCreate: boolean;
getSomeFunction(): Observable<boolean> {
return new Observable<boolean>(
observer => {
const canCreate = null; // this is just null for now, will some value later
if (canCreate == null) {
observer.error('error');
} else {
observer.next(true);
}
observer.complete();
}
)
}
this.getSomeFunction()
.do((value) => {
this.cCreate = value;
}, (error) => {
console.log(error + new Date().toTimeString());
})
.delay(1000)
.retry(10)
.subscribe(
value => this.cCreate = value,
error => {
this.cCreate = false;
},
() => {}
);
}
and console result is :
Upvotes: 59
Views: 79961
Reputation: 273
Finally, I figured out how to filter some errors, wait to retry, and limit the retries.
The time-out could be easily incremental.
import {
HttpErrorResponse,
HttpEvent,
HttpHandlerFn,
HttpRequest,
} from '@angular/common/http';
import { Observable, retry, throwError, timer } from 'rxjs';
const count = 2;
const timeout = 1000;
const resetOnSuccess = false;
function delay(error: HttpErrorResponse, retries: number): Observable<number> {
const canRetryStatus = error.status === 0 || error.status >= 500;
const canRetryCounter = retries < count;
const shouldRetry = canRetryStatus && canRetryCounter;
return shouldRetry ? timer(timeout) : throwError(() => error);
}
export function ErrorInterceptor(
req: HttpRequest<unknown>,
next: HttpHandlerFn
): Observable<HttpEvent<unknown>> {
return next(req).pipe(retry({ count, delay, resetOnSuccess }));
}
Upvotes: 0
Reputation: 683
Retry 5 times with a delay of 500ms:
retryWhen
's error Observable to timer
or throwError
retryWhen(concatMap((err, index) => index < 5 ? timer(500) : throwError(err)))
retryWhen
's error Observable, use delayWhen
to wait timer
or throwError
retryWhen(delayWhen((err, index) => index < 5 ? timer(500) : throwError(err)))
or you can do this :
const delayFn = (delay = 0, count = Infinity) => {
return (err, index) => index < count ? timer(delay) : throwError(err);
};
retryWhen(concatMap(delayFn(500, 5))) // RxJS 6.x
retryWhen(delayWhen(delayFn(500, 5))) // RxJS 6.3 ⬆
retry
retry({ count: 5, delay: 500 })
Upvotes: 20
Reputation: 717
As the accepted answer's suggestion to use retryWhen() was ok at that time, now, as we can see on the provided link for retryWhen(), this operator is deprecated and will be removed as from the RxJS version 9 or 10. So all solutions with retryWhen() are deprecated as well.
Now a solution that is suggested by the RxJS dev team and the docs is to use retry() operator, which is also an error handling operator, with the retryConfig param.
Now, to achieve the same logic, with less code, you would use the following:
// A custom method to check should retry a request or not
shouldRetry(error: HttpErrorResponse) {
// Example for catching specific error code as well
if (error.status === 503) {
return timer(1000); // Adding a timer from RxJS to return observable to delay param.
}
throw error;
}
// Retry operator somewhere in your code
retry({ count: 2, delay: this.shouldRetry })
As you could see from the example above, we have used less code and less RxJS operators to achieve some logic for which we needed a lot more using the retryWhen() operator. This is also the case why the team is deprecating this operator, as the same logic can be achieved using the retry() with this config.
Note: you would have to adopt the code I provided as per your needs, but this is an example of how to use the retry() operator instead of retryWhen().
Upvotes: 35
Reputation: 9
1. Using Concat
getPosts()
{
return this.httpClient
.get<any[]>('https://jsonplaceholder.typicode.com/postss')
.pipe(
retryWhen(errors =>{
return concat(
errors.pipe(
delay(2000),
take(2),
),
throwError("Max Retries Exceeded!")
)
}),
catchError(()=>of(["Angular","Rxjs"])))
}
2. Using ConcatMap
return this.httpClient
.get<any[]>('https://jsonplaceholder.typicode.com/postss')
.pipe(
retryWhen(errors =>{
return errors.pipe(
concatMap((error,index)=> {
if(index>=2) return throwError(error);
else return of(error).pipe(delay(2000))
})
)
}),
catchError(()=>of("Angular","Rxjs"])))
3. Using Scan
return this.httpClient
.get<any[]>('https://jsonplaceholder.typicode.com/postss')
.pipe(
retryWhen(errors =>{
return errors.pipe(
scan( (acc, error) => {
if(acc>2) throw error;
return acc+1;
}, 1),
delayWhen(val => timer(val * 1000)),
)
}),
catchError(()=>of("Angular","Rxjs"])))
}
Upvotes: 0
Reputation: 56
As accepted answer rethrow didn't work for me, I ended up with this:
Retry 3 times with 2 seconds delay and if no success throw an error.
Observable.pipe(
retryWhen(errors => errors.pipe(
mergeMap((error, i) =>
iif(() => i >= 3, throwError(() => error), timer(2000))
)
))
);
Upvotes: 1
Reputation: 1420
I came to this conclusion, in order to retry with other operations in the http pipe
import {delay as _delay, map, retryWhen} from 'rxjs/operators';
export const delayedRetry = (delay, retries = 1) => retryWhen(result => {
let _retries = 0;
return result.pipe(
_delay(delay),
map(error => {
if (_retries++ === retries) {
throw error;
}
return error;
}),
);
},
);
Usage
http.pipe(
delayedRetry(1500, 2),
catchError((err) => {
this.toasterService.error($localize`:@@not-saved:Could not save`);
return of(false);
}),
finalize(() => this.sending = false),
).subscribe((success: boolean) => {
if (success === true) {
this.toasterService.success($localize`:@@saved:Saved`);
}
}
});
Upvotes: 4
Reputation: 165
RxJS provides retry
operator that resubscribes the Observable for the given number of count when there is an error. Before throwing error Observable is resubscribed for the given number of count by retry
operator and if still there is an error, then error is thrown. retry
is useful to hit the URL many times. It is possible that because of network bandwidth, URL does not return successful data in one time and when it reties, it may return data successfully. If after retying still there is error in Observable then catchError
can be used to return Observable with user defined default data.
getBook(id: number): Observable<Book> {
return this.http.get<Book>(this.bookUrl + "/" + id).pipe(
retry(3),
catchError(err => {
console.log(err);
return of(null);
})
);
}
Upvotes: 1
Reputation: 641
Works on rxjs version 6.3.3
Open Console and see the retries
Sample Code
import { map, catchError, retryWhen, take, delay, concat } from 'rxjs/operators';
import { throwError } from 'rxjs';
export class ApiEXT {
static get apiURL(): string { return 'http://localhost:57886/api'; };
static httpCLIENT: HttpClient;
static POST(postOBJ: any, retryCOUNT: number = 0, retryINTERVAL: number = 1000) {
return this.httpCLIENT
.post(this.apiURL, JSON.stringify(postOBJ))
.pipe(
map(this.handleSUCCESS),
retryWhen(errors => errors.pipe(delay(retryINTERVAL), take(retryCOUNT), concat(throwError("Giving up Retry.!")))),
catchError(this.handleERROR));
}
private static handleSUCCESS(json_response: string): any {
//TODO: cast_and_return
return JSON.parse(json_response);
}
private static handleERROR(error: Response) {
let errorMSG: string;
switch (error.status) {
case -1: errorMSG = "(" + error.status + "/" + error.statusText + ")" + " Server Not Reachable.!"; break;
default: errorMSG = "(" + error.status + "/" + error.statusText + ")" + " Unknown Error while connecting with server.!"; break;
}
console.error(errorMSG);
return throwError(errorMSG);
}
}
Upvotes: 3
Reputation: 9815
All of this is RxJS 6+
You could use the fully tested operator from this package, or scroll down to see the source :)
npm i rxjs-boost
import { retryWithDelay } from 'rxjs-boost/operators';
obs$.pipe(
// will retry 4 times with a 1s delay before each try:
retryWithDelay(1000, 4)
);
As most (or maybe none) of the other answer didn't meet all of my criteria, I'll list my solution below. Goals:
x
times if an error is thrown. ✅y
before each retry. ✅strict: true
– but this was quite difficult to mess up. ✅As every other answer we'll use the retryWhen operator to catch the errors. To track the amount of repetitions can use the scan operator. To limit the amount of repetitions we'll simply throw an error inside a map operator.
The original source uses throwIf, but in that case we could simply use retryWithDelay from rxjs-boost.
Last we'll use the delay operator to add the delay between the different executions:
import { MonoTypeOperatorFunction } from 'rxjs';
import { delay as delayOperator, map, retryWhen, scan } from 'rxjs/operators';
export function retryWithDelay<T>(
delay: number,
count = 1
): MonoTypeOperatorFunction<T> {
return (input) =>
input.pipe(
retryWhen((errors) =>
errors.pipe(
scan((acc, error) => ({ count: acc.count + 1, error }), {
count: 0,
error: undefined as any,
}),
map((current) => {
if (current.count > count) {
throw current.error;
}
return current;
}),
delayOperator(delay)
)
)
);
}
Upvotes: 9
Reputation: 71
I recently had this problem, and found that the accepted solution could be improved.
Observable.pipe(
retryWhen(errors => errors.pipe(
delay(1000),
take(10))),
first(v => true),
timeout(10000))
What it essentially does is to retry as mentioned, but this finishes right away without adding any (erroneous) value using the 'first' operator.
If it cannot find a value within the timeout timeframe, an error is raised.
Upvotes: 3
Reputation: 692181
delay()
is used to introduce a delay between events emitted by the observable. But the observable never emits any event. It just errors immediately.
What you're looking for is retryWhen()
, which allows deciding after how long to retry:
RxJS 5:
.retryWhen(errors => errors.delay(1000).take(10))
RxJS 6:
import { retryWhen, delay, take } from 'rxjs/operators'
someFunction().pipe(
// ...
retryWhen(errors => errors.pipe(delay(1000), take(10)))
)
This will complete the whole observable after 10 attempts. If you want to error the whole observable after 10 attempts, the observable returned by the retryWhen callback must throw:
RxJS 5:
.retryWhen(errors => errors.delay(1000).take(10).concat(Observable.throw()))
RxJS 6:
import { retryWhen, delay, take, concatMap, throwError } from 'rxjs/operators'
someFunction().pipe(
// ...
retryWhen(errors => errors.pipe(delay(1000), take(10), concatMap(throwError)))
)
Upvotes: 117
Reputation: 403
For ngrx5+ we could create operator:
function retryRequest(constructor: () => Observable, count: number, delayTime: number) {
let index = 0;
return of(1) // we need to repeat not the result of constructor(), but the call of constructor() itself
.pipe(
switchMap(constructor),
retryWhen(errors => errors.pipe(
delay(delayTime),
mergeMap(error => {
if (++index > count) {
return throwError(error);
}
return of(error);
})
))
);
}
Upvotes: 3
Reputation: 816
To add on to @JB Nizet's answer. If you're writing this in rxjs 5+ with lettable operators, structure it as
retryWhen(errors => errors.pipe(delay(1000), take(5)))
Upvotes: 27
Reputation: 8804
I come up with following solution using retryWhen
and Observable.Interval
, but in this solution, error
function of subscribe never calls,
this.branchService.getCanCreate()
.do((value) => {
this.cCreate = value;
}, (error) => {
console.log('do', error + new Date().toTimeString());
})
.retryWhen(errors => {
return Observable.interval(1000).take(3).concat(Observable.throw('error')));
})
.subscribe(
value => {
this.cCreate = !!value
console.log('success', new Date().toTimeString());
},
error => {
console.log('subscribe', error + new Date().toTimeString());
this.cCreate = false;
},
() => {
console.log('finally', new Date().toTimeString());
}
);
Upvotes: 0
Reputation: 2678
This may help you
let values$ = Rx.Observable.interval(1000).take(5);
let errorFixed = false;
values$
.map((val) => {
if(errorFixed) { return val; }
else if( val > 0 && val % 2 === 0) {
errorFixed = true;
throw { error : 'error' };
} else {
return val;
}
})
.retryWhen((err) => {
console.log('retrying again');
return err.delay(1000).take(3); // 3 times
})
.subscribe((val) => { console.log('value',val) });
Upvotes: 1