Pranay Rana: RxJs
Showing posts with label RxJs. Show all posts
Showing posts with label RxJs. Show all posts

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.  

Sunday, April 22, 2018

Angular Dependent Request

In software application, screen(s) where to display data application needs to make dependent request i.e. First have to make Request A (to server or database), based on result of A request data have to make Request B.

Example : In shopping application, on product screen , when user select product it will display product details and based on product make region it also display in which region it available or which region it's available.

So achieve in application there is need of dependent request , which works this way

  1.  It request (server/database) to get full product detail
  2. Based on product region , it makes request (server/database) to get available regions  

In code most of the developer write below code

this.productService.getProduct(1)
 .subscribe(data => {
          this.product = data;
          this.productService.getAvailableRegion(data.regionId)
                   .subscribe(region => this.avilableRegion = region);
}); 

code above does the task. But its complex (in terms of readability)  and not easy to understand when there is more depedent request are there  (if there are more dependent request like Request C , depends on Request B and which depends on Request A) as it goes deep and deep for each request.

Below are some way to avoid complexity and easy to understand code.

RxJs Way
Above example just make use of one RxJs function which subscribe, and does thing in that function only. For second request (dependent request) it does same thing which makes code complex. But RxJS provides number of functions apart of subscribe  which make things simple. Below is code how to reduce complexity of above code by making use of other function provided by Rxjs.

this.productService.getProduct(id)
  .pipe(
       map(data => this.product = data),
       switchMap (cust => this.productService.getAvailableRegion(this.product.regionId) )
   ).subscribe(region => this.avilableRegion = region); 

this does the same task as above code, but better way. Following functions to not down which makes code easy
  1. pipe - its RxJs function which allows number of RxJS function as input and combine them to perform operations one by one on observable received from previous function. 
    In above code it combines map and switchMap RxJs operator.
  2. map - it used to map returned observable value to other value. In this case it maps returned response to product.
  3. switchMap - it used for switching from one observable to other observable, in this example it switch from product to list of regions.
  4. Finally subscribe - this allows to subscribe to response observable stream of provided from pipe function after operation done in it.
Advance Typescript (async/await on Promise)

Another way to achieve same is make use of advance typescript concept , which making use of async/await. Below is code for the same

async getDetails(id) {
   this.product = await this.productService.getProduct(id).toPromise();
   this.avilableRegion = await this.productService.getAvailableRegion(this.product.Id).toPromise();
} 

Points to note with this approach is:

  1. async/await is used with Promise object only. 
  2. In code, promise object got created from returned observable by making use of toPromise method.

when method getDetails called from anywhere in code, executing stops when await keyword got encountered, and it waits till getProduct server request returns. Execution get resumed once server returned response which in this case return product. Then it stops execution again on encounter of await, which used to wait till availableResion request retuns.

At the End 

So there are two approach

  1. RxJs and 
  2. TypeScript 
none of them is better then another one , it all on developer preference. But both the way it makes code simple and easy to understand. It also help developer in maintaining code for long time as not much complexity involved.