Http nested request


#1

Hello,

I make a request and want that only the nested requests are made when the first returns a http status code 303.
My backend returns 303 when session expired and I want that after successfull login the request for fetching person information is made again.
So is it possible to say that the nested request is only made when the request before has status code 303?

  getPersons() {
    return this.http.get(URL_PERSONS).map(res => {
      if (res.status === 303) {
        return res;
      } else {
        return  res.json();
      }
    })
      .flatMap((res) => {
        if (res.status === 303) {
          return this.login();
        }else {
          return res;
        }
      }).flatMap(res => {
        return this.http.get(URL_PERSONS);
      });
  }

#2

Something like this?

const sendRequest = () => this.http.get(URL_PERSONS);
const firstTry$ = sendRequest();
// Only emits next when firstTry$ was not successful
const resWithLogin$ = firstTry$
  .filter(res => res.status === 303)
  .flatMap(res => this.login())
  .flatMap(sendRequest);
// Only emits next when firstTry$ was successful
const res$ = firstTry$
  .filter(res => res.status !== 303);
// Combines both streams and maps res.body to json
const result$ = Observable
  .merge(res$, resWithLogin$)
  .map(res => res.json());

#4

Maybe that is easier to understand:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/merge';
import { Observable } from 'rxjs/Observable';

const URL_PERSONS = 'http://example.de/api/persons';

@Injectable()
export class MyProvider {
  constructor(private http: Http) { }

  getPersons(): any {
    // Requests persons from api
    const getPersons = () => this.http.get(URL_PERSONS);

    // First try to get persons
    const response = getPersons();

    // Stream emits when status 200
    const result = response
      .filter(res => res.status === 200)
      .map(res => res.json());
    // Stream emits when status 303
    const notAuthorized = response
      .filter(res => res.status === 303);
    // Stream emits when status not 200 or 303
    const failure = response
      .filter(res => res.status !== 303 && res.status !== 200);

    // Maps 303 to new request
    const resultAfterAuth = notAuthorized
      .mergeMap(res => this.login()) // mergeMap is the same as flatMap
      .flatMap(getPersons);

    // Stream emits an error, if status wasn't 200 or 303.
    const errorResult = failure
      .mergeMap(res => Observable.throw('Something went wrong'));

    // Combines all streams and returns the result as a stream
    return Observable
      .merge(
      result,
      resultAfterAuth,
      errorResult
      );
  }

  login(): Observable<boolean> {
    return Observable.of(true);
  }
}

#5

ok I try it… and thanks you for the detailed explanation.
It works fine except the fact that when I call the getPersons in my page and an error ocurred like 404, it goes in the success block and not in the error block which I except because Observable.throw(‘Something went wrong’) is thrown.
But I litte change your code… to classify the error messages…

ngOnInit(){
  this.backend.readPersons().subscribe(
        success => console.log(success),
        error => {
          console.log(error);
        },
        () => console.log('dismissed')
      );
    }
}

 getPersons(): any {
    // Requests persons from api
    const getPersons = () => this.http.get(URL_PERSONS);

    // First try to get persons
    const response = getPersons();

    // Stream emits when status 200
    const result = response
      .filter(res => res.status === 200)
      .map(res => res.json());
    // Stream emits when status 303
    const notAuthorized = response
      .filter(res => res.status === 303);
    // Stream emits when status not 200 or 303
    const failure404 = response
      .filter(res => res.status === 404).map(()=>Observable.throw('404 occured'));
    const failureOthers = response
      .filter(res => res.status !== 404).map(()=>Observable.throw('Others occured'));

    // Maps 303 to new request
    const resultAfterAuth = notAuthorized
      .mergeMap(res => this.login()) // mergeMap is the same as flatMap
      .flatMap(getPersons);

    // Combines all streams and returns the result as a stream
    return Observable
      .merge(
      result,
      resultAfterAuth,
      failure404,
      failureOthers 
      );
  }

  login(): Observable<boolean> {
    return Observable.of(true);
  }
}


#6

const failureOthers = response .filter(res => res.status !== 404).map(()=>Observable.throw('Others occured'));
will have all responses that are not 404. Even 200 and 303. Is that, what you want?

You can also work with async await and promises, if you don’t like Observables:

async getPerson(): Promise<any> {
   const sendRequest = () => this.http.get(URL_PERSONS).toPromise();
   const response = await sendRequest();
   switch(response.status) {
    case 200: 
      return response.json();
    case 401:
      throw new Error('Unauthorized');
    case 404:
      throw new Error('File not found');
    ...
  }
}

#7

My problem is…
For each StatusCode I will show a user friendly message. It works fine now.
The only thing that does not works is to recognize Timeout errors.

    const timeout = response
      .filter((res) =>res instanceof TimeoutError)
      .mergeMap(() => {
        return Observable.throw('Timout occured');
      });
    ;

What will be wrong here?


#8

Try to simulate a server timeout and console.log the response. Then check if the response is really a TimeoutError. Also log the output of errors.

response.subscribe(res => console.log('Response', res), e => console.log('Error', e));

#9

yeah it’s a TimoutError, just fix it so.

 return Observable
      .merge(
      result,
      resultAfterAuth,
      errorResult
      ).
.catch((err) => {
        if (err instanceof TimeoutError) {
          return Observable.throw('Timout occured');
        }
        return Observable.throw(err);
      });

#10

Hello,

I see that for each item in the merge a backend call is made.
In this example, in all 4.
How can I fix it?