Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I allow my HTTP request to continue propagating through my app after a caught failure?

I'm using Ionic 2 which sits on top of Angular 2.

I am using a service in my app to handle all API requests. Some of those requests carry an Authorization header. My API can return a 401 if the auth fails, and I am currently handling that by showing an error to the user and navigating them to the login screen. That all works great and I publish an event for these errors which I subscribe to elsewhere to handle the logic (as I can't use navigation controllers in a service).

Code for api.js service:

@Injectable()
export class Api {
  base_url: string = 'https://***.com';
  url: string = this.base_url + '/api/v1';
  authurl: string = this.base_url + '/oauth/token';
  grant_type: string = 'password';
  client_id: string = '1';
  client_secret: string = '***';
  access_token: string;

  constructor(
    public http: Http, 
    private storage: Storage,
    public events: Events) {

    // Grab access token and store it
    storage.get('access_token').then((val) => {
      this.access_token = val;
    });
  }

  // Performs a GET request with auth headers
  get(endpoint: string, params?: any) {
    if(!params) {
      params = [];
    }
    params['grant_type'] = this.grant_type;
    params['client_id'] = this.client_id;
    params['client_secret'] = this.client_secret;

    let headers: Headers = this.getHeaders();

    return this.getApiToken().flatMap(data => {

      headers.append('Authorization', 'Bearer ' + data);

      let options = new RequestOptions({ headers: headers });

      // Support easy query params for GET requests
      if (params) {
        let p = new URLSearchParams();
        for (let k in params) {
          p.set(k, params[k]);
        }
        // Set the search field if we have params and don't already have
        // a search field set in options.
        options.search = !options.search && p || options.search;
      } 

      return this.http.get(this.url + '/' + endpoint, options)
        .catch((error: any) => {
          if (error.status === 500) {
            this.events.publish('api:generalError', error);
            return Observable.throw(new Error(error.status));
          }
          else if (error.status === 401) {
            this.events.publish('api:unauthorized', error);
            return Observable.throw(new Error(error.status));
          }
        });
    }).share();
  }
}

The problem is down to the 'Loading...' dialog that I show to the user while the get request is taking place. Before the method is called I create a loading dialog and dismiss it on success or failure. The problem is that I don't have any scope of this inside api.js in order to dismiss it when a 401 or 500 is caught.

Here's a sample of my logic around this:

let loader = this.loadingCtrl.create({
  content: "Please wait..."
});
loader.present();

this.trainingProgramme.get_programmes()
  .map(res => res.json())
  .subscribe((res) => {

  this.currentItems = res.training_programmes;

}, (err) => {
  // Error
  console.log(err);
}, () => {
  loader.dismiss();
});

I don't think it's important, but I also have a service for each entity which in turn calls api.js service. In the above example it's this.trainingProgramme, which looks like this:

  get_programmes() {
    let seq = this.api.get('training-programmes');

    seq
      .subscribe();

    return seq;
  }

I thought the way I approached this was all correct, however I can't see a way I can handle the 'Loading' issue.

Is there a way I can have the get method continue within my app even after an error has been caught, so that my loader.dismiss() code is ran within the correct scope?

I really don't want to have to use the loader inside the service (not sure I'm even able to?) as this seems like bad design, and I don't want to always show a loader, so it belongs in the controller.

like image 521
Mike Avatar asked Aug 02 '17 11:08

Mike


People also ask

How do you handle errors in catch block?

You can put a try catch inside the catch block, or you can simply throw the exception again. Its better to have finally block with your try catch so that even if an exception occurs in the catch block, finally block code gets executed.

How do you handle errors in logic apps?

To achieve detecting an error event and then providing logic to handle the error, we will configure a parallel action below our custom connector call. Within each stream of the parallel action we then specify our Configure run after settings.

How do you handle errors unexpected cases in Web API?

Using Exception Filters Exception filters can be used to handle unhandled exceptions which are generated in Web API. The exception filter can be able to catch the unhandled exceptions in Web API. This filter is executed when an action method throws the unhandled exception.

How do you handle error Express?

The simplest way of handling errors in Express applications is by putting the error handling logic in the individual route handler functions. We can either check for specific error conditions or use a try-catch block for intercepting the error condition before invoking the logic for handling the error.


1 Answers

What you need is the .finally operator, which will execute always even if there's an error on the Observable.

The complete callback (the last from the .subscribe call) will not trigger if an error happens.

Remember to include it:

import 'rxjs/add/operator/finally';

...

this.trainingProgramme.get_programmes()
 .finally(() => loader.dismiss())
 .map(res => res.json())
 .subscribe((res) => {
   ...
 }
 , (err) => {
  // Error
  console.log(err);
 });

By the way, consider updating your code from @angular/http to the new HttpClient.

like image 50
Edmundo Rodrigues Avatar answered Nov 03 '22 22:11

Edmundo Rodrigues