Pranay Rana

Tuesday, June 12, 2018

Way to handle Parallel Multiple Requests

In web application Multiple requests means when there is need of making more then one Http requests to get result response and display to end user on web page. Multiple requests can be
  • Dependent Request
    Http Request is dependent request when it waits for result which needs to be given by other Http request i.e. parent request. Read more about how to perform Dependent request in angular here : Angular Dependent Request
  • Parallel Request
  • When client code fire more then one Http request on server and that all fired request get executed on server simultaneously.
Parallel Http requests are required when application need to make simultaneously to get data and display result to end user. For Example - In online shopping application, on order history page when use clicks on old Order to get detail , details page should display order details with all products detail which is associated with order.
Mostly in application database when order get saved its will save order details with associated product ids not full product. so to achieve this requirement once order data available with product ids, client code need to get fire multiple requests basically for each product id to get detail of product associated with order.

Below picture shows how parallel request get executed by approach going to discuss below.




Below are the way client code make parallel Http request and handle response of the requests to display final result with help of RxJs functions.

1. MergeMap

RxJs function mergeMap allows to fire out multiple request and handle response of each request to produce result. Function flatten inner observable(s) and allow to control each observable. Read more : MergeMap

    import { mergeMap, catchError } from 'rxjs/operators'; 
    megeMapTest() {
    const prouctIds: number[] = [1, 2, 3];
    const requests = from(prouctIds)
      .pipe(
      mergeMap(id => this.productService.getProduct(id))
      );

    requests.subscribe(
      data => console.log(data), //process item or push it to array 
      err => console.log(err));
 } 

In above code, important thing to note out is mergeMap, it merges all parallel requests fired to get product in one observable stream (array of observable) and then allows that request to handle individually.


Advantage
a. One of the advantage with mergemap is, it create single stream of observable and allows to process response of each request individually.
So if there is any request fails i.e. throws server side error, then it doesn't affect other parallel requests. Failed request(s) call error function in above code and succeeded request calls success function. (Which is advantage over second approach i.e. forkJoin function discussed below)

b. mergmap function start processing request as it get response i.e. in case of multiple parallel request it process request which completes first ,doen't wait for other request to get completed. (Which is advantage over second approach i.e. forkJoin function discussed below)

Disadvantage
a. It doesn't preserve sequence (i.e. order in which it fire) of requests, means if there are 10 request fired parallelly then which request complete first will display result first.

Solution to this is

    import { mergeMap, catchError } from 'rxjs/operators'; 
    list: Product[] = [];
    megeMapTest() {
    const prouctIds: number[] = [1, 2, 3];
    const requests = from(prouctIds)
      .pipe(
      mergeMap(id => this.productService.getProduct(id))
      );

      requests.subscribe(
      data => {
        this.list.push(data);

        this.list.sort((a: Product, b: Product) => {
          const aIndex = prouctIds.findIndex(id => id === a.Id);
          const bIndex = prouctIds.findIndex(id => id === b.Id);
          return aIndex - bIndex;
        });
      }, //process item or push it to array 
      err => console.log(err));
 } 

above solution code sort order of received response using index of array.

b. megeMap only merge request of same return type only. To understand this have look to above code again, all request is going to return observable of Product type which is going to be return by getProduct method of productservice.

Making use of forkJoin function can resolve this issue. Which is second approach discussed below.

2. ForkJoin

RxJs function forkJoin take one or more observable as input and provide steam of observable as result, which contains value last value emitted by each  inputted observable to function. Read more : forkJoin

import { catchError } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
import { forkJoin } from 'rxjs/observable/forkJoin';
  forkJoinTest() {
    const prouctIds: number[] = [1, 2, 3];
    const productRequests = prouctIds.map(id => this.productService.getProduct(id).pipe(
      catchError(error => of(`Bad Promise: ${error.message}`))
    ));

    const requests = forkJoin(productRequests);
    requests.subscribe(
      data => console.log(data) //process item or push it to array 
    );
  } 

In above code, as per definition forkJoin takes observable requests as input and returns result.

Advantage
a. forkJoin  reserve sequence (i.e. order in which request sent), so order in which requests inputted to forkJoin in same order it gives response result.

b. forkJoin can take multiple request which returns different type observable.

async forkJoinTest() {
    const customerRequest =this.customerService.getCustomer(1).pipe(
      catchError(error => of(`Bad request: ${error.message}`))
    );    
    const orderRequest =this.orderService.getOrder(1).pipe(
      catchError(error => of(`Bad request: ${error.message}`))
    ); 
    const productRequest =this.productService.getProduct(1).pipe(
      catchError(error => of(`Bad request: ${error.message}`))
    );

    const requests = await forkJoin(customerRequest,orderRequest,productRequests).toPromise();
    
    console.log('Customer ' + requests[0]);
    console.log('order ' + requests[1]);
    console.log('product ' + requests[2]);
  } 

In above code , forkJoin takes three requests (customer, product, order) which returns different type of observable. Also code make use of async/await advance typescript/javascript concept, which allows to wait till request completes. (Which is advantage over first approach i.e. mergeMap function discussed below)

Disadvantage
a. forkJoin waits for all inputted Http requests to get completed before calling subscribe function. So even though some request get completed it has to wait till other or lets say last request to complete. (This can be avoided with mergeMap function as it process requests as it get completed).

b. if one of the inputted request to forkJoin fails , then forkJoin also fails and return error even other inputted request completed successfully. To check run code as below and return error from server

import { catchError } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
import { forkJoin } from 'rxjs/observable/forkJoin'; 
  forkJoinTest() {
    const prouctIds: number[] = [1, 2, 3];
    const productRequests = prouctIds.map(id => this.productService.getProduct(id));

    //.pipe(
    //catchError(error => of(`Bad Promise: ${error.message}`))
    //));

    const requests = forkJoin(productRequests);
    requests.subscribe(
      data => console.log(data), //process item or push it to array
      err => console.log(err) 
    );
  } 

In above code catchError function is commented, that means if any of request fails then forkJoin also fails and return that error. (this is not case with mergeMap)
To avoid the make use of catchError function as given in above code.

Which one is better
Both of them is not good or not bad. It all depends on requirement at the end. Because there might be case when have requirement to wait for all request to complete or want to parallely executed request which return different value or do not want to produce result if one of sent requests fails then go for forkJoin and there might be case when have requirement for do not wait till all request get compete or do not want to reserve order of request or want to process requests even one of the request fails then go for mergeMap.