HI WELCOME TO Sirees

Observable retry on error

Leave a Comment

In this video we will how to resubscribe and retry an Observable if there is an error.


Please change getEmployeeByCode() method in employee.service.ts file to return an Observable instead of a Promise as shown below.

getEmployeeByCode(empCode: string): Observable<IEmployee> {
    return this._http.get("http://localhost:24535/api/employees/" + empCode)
        .map((response: Response) => <IEmployee>response.json())
        .catch(this.handleError);
}


To cause an error, stop the Web API Service. At this point if you navigate to http://localhost:12345/src/employees/emp101, the application display a message stating - Problem with the service. Please try again after sometime. In the "Console" tab of the "Browser Developer Tools" you will see - ERR_CONNECTION_REFUSED.
Observable retry on error

To resubscribe to the Observable and retry, use the rxjs retry operator. The changes in employee.component.ts file are commented and self explanatory.

import { Component, OnInit } from '@angular/core';
import { IEmployee } from './employee';
import { EmployeeService } from './employee.service';
import { ActivatedRoute } from '@angular/router';
import { Router } from '@angular/router';

// Import rxjs retry operator
import 'rxjs/add/operator/retry';


@Component({
    selector: 'my-employee',
    templateUrl: 'app/employee/employee.component.html',
    styleUrls: ['app/employee/employee.component.css']
})
export class EmployeeComponent implements OnInit {
    employee: IEmployee;
    statusMessage: string = 'Loading data. Please wait...';
    retryCount: number = 1;

    constructor(private _employeeService: EmployeeService,
        private _activatedRoute: ActivatedRoute,
        private _router: Router) { }

    ngOnInit() {
        let empCode: string = this._activatedRoute.snapshot.params['code'];

        this._employeeService.getEmployeeByCode(empCode)
            // Chain the retry operator to retry on error.
            .retry()
            .subscribe((employeeData) => {
                if (employeeData == null) {
                    this.statusMessage =
                        'Employee with the specified Employee Code does not exist';
                }
                else {
                    this.employee = employeeData;
                }
            },
            (error) => {
                this.statusMessage =
                    'Problem with the service. Please try again after sometime';
                console.error(error);
            });
    }

    onBackButtonClick(): void {
        this._router.navigate(['/employees']);
    }
}

The downside of this approach is that the application keeps on retrying forever. If we start the Web API service, the call succeeds and the observable completes with employee data displayed on the web page.

Now if your requirement is not to retry forever, but only retry for a specific number of times if there is an error then we can use another variation of retry as shown below. Notice in this case we are passing number 3 to the retry opertaor indicating that we only want to retry 3 times. After the 3rd attempt the observable completes with an error.

ngOnInit() {
    let empCode: string = this._activatedRoute.snapshot.params['code'];

    this._employeeService.getEmployeeByCode(empCode)
        // Retry only 3 times if there is an error
        .retry(3)
        .subscribe((employeeData) => {
            if (employeeData == null) {
                this.statusMessage =
                    'Employee with the specified Employee Code does not exist';
            }
            else {
                this.employee = employeeData;
            }
        },
        (error) => {
            this.statusMessage =
                'Problem with the service. Please try again after sometime';
            console.error(error);
        });
}

The problem with the retry operator is that, it immidiately retries when there is an error. In our case it is a connection issue with the service. Retrying again immediately in our case does not make much sense, as most likely it might fail again. So in situations like this we may want to retry after a short delay, may be after a second or so. This is when we use the retryWhen rxjs operator. retryWhen operator allows us to specify delay in milli-seconds and can be used as shown below. Please donot forget to import retryWhen and delay operators.

import 'rxjs/add/operator/retrywhen';
import 'rxjs/add/operator/delay';

ngOnInit() {
    let empCode: string = this._activatedRoute.snapshot.params['code'];

    this._employeeService.getEmployeeByCode(empCode)
        // Retry with a delay of 1000 milliseconds (i.e 1 second)
        .retryWhen((err) => err.delay(1000))
        .subscribe((employeeData) => {
            if (employeeData == null) {
                this.statusMessage =
                    'Employee with the specified Employee Code does not exist';
            }
            else {
                this.employee = employeeData;
            }
        },
        (error) => {
            this.statusMessage =
                'Problem with the service. Please try again after sometime';
            console.error(error);
        });
}

Now if you are wondering can't we use delay opertor with retry operator, the answer is NO, we can't. The delay operator in the following example will not work as expected. As you can see from the browser console, it immediately retries instead of waiting for 5 seconds before a retry attempt.

ngOnInit() {
    let empCode: string = this._activatedRoute.snapshot.params['code'];

    this._employeeService.getEmployeeByCode(empCode)
        // The delay operator will not work with retry
        .retry().delay(5000)
        .subscribe((employeeData) => {
            if (employeeData == null) {
                this.statusMessage =
                    'Employee with the specified Employee Code does not exist';
            }
            else {
                this.employee = employeeData;
            }
        },
        (error) => {
            this.statusMessage =
                'Problem with the service. Please try again after sometime';
            console.error(error);
        });
}

If you want to retry every 1000 milli-seconds only for a miximum of 5 times then we can use rxjs scan operator along with the take operator. While retrying we also want to show the retry attempt number to the user on the web page as shown below.
observable retry example

After all the retry attempts are exhausted, the application should stop retrying and display the error message to the user as shown below.
observable retry with delay

If the connection becomes available between the retry attempts, the application should display employee data.

Here is the complete code.

import { Component, OnInit } from '@angular/core';
import { IEmployee } from './employee';
import { EmployeeService } from './employee.service';
import { ActivatedRoute } from '@angular/router';
import { Router } from '@angular/router';

import 'rxjs/add/operator/retrywhen';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/scan';

@Component({
    selector: 'my-employee',
    templateUrl: 'app/employee/employee.component.html',
    styleUrls: ['app/employee/employee.component.css']
})
export class EmployeeComponent implements OnInit {
    employee: IEmployee;
    statusMessage: string = 'Loading data. Please wait...';
    retryCount: number = 1;

    constructor(private _employeeService: EmployeeService,
        private _activatedRoute: ActivatedRoute,
        private _router: Router) { }

    ngOnInit() {
        let empCode: string = this._activatedRoute.snapshot.params['code'];

        this._employeeService.getEmployeeByCode(empCode)
            // Retry 5 times maximum with a delay of 1 second
            // between each retry attempt
            .retryWhen((err) => {
                return err.scan((retryCount, val) => {
                    retryCount += 1;
                    if (retryCount < 6) {
                        this.statusMessage = 'Retrying...Attempt #' + retryCount;
                        return retryCount;
                    }
                    else {
                        throw (err);
                    }
                }, 0).delay(1000)
            })
            .subscribe((employeeData) => {
                if (employeeData == null) {
                    this.statusMessage =
                        'Employee with the specified Employee Code does not exist';
                }
                else {
                    this.employee = employeeData;
                }
            },
            (error) => {
                this.statusMessage =
                    'Problem with the service. Please try again after sometime';
                console.error(error);
            });
    }

    onBackButtonClick(): void {
        this._router.navigate(['/employees']);
    }
}

In our next video, we will discuss how to allow the end user to cancel retry attempts at any point using the unsubscribe method.

0 comments:

Post a Comment

Note: only a member of this blog may post a comment.