hsunny study blog

RxJS를 이용하여 HTTP 통신 개선하기 본문

programming/RxJS

RxJS를 이용하여 HTTP 통신 개선하기

헤써니 2019. 5. 27. 23:39

개발 중인 IONIC 앱에서는 Angular에서 제공하는 HttpClientModule을 이용하여 서버와 통신하고 있습니다.

 

// in app/app.module.ts (excerpt)
import { HttpClientModule } from '@angular/common/http';
// in app/config/config.service.ts (excerpt) ==> provider In IONIC
import { HttpClient } from '@angular/common/http'
view raw http.ts hosted with ❤ by GitHub

HttpClientModuleAngular 4.3.0 버전에 추가되었습니다. 
Angular에서 제공하는 HTTP 모듈은 RxJS Observable 객체를 기반으로 만들어졌습니다. RxJS이지만 RxJS의 장점들을 이용하지 못하고 단순히 결과를 받는 데에만 이용하고 있었습니다.

이 부분에 대해 어떻게 개선해나갔는지 내용을 공유합니다.

 

기존

// home.providers.ts
home(myData: object): Observable<any> {
let url = this.urlConfig.getURL();
return this.httpClient
.post(url, myData)
.timeout(20000);
}
view raw original.ts hosted with ❤ by GitHub

 

이런 생각을 시작으로 코드를 개선해나갔습니다.

- 통신에 실패하는 이유를 timeout으로만 두어도 될까
- http 응답에 따라 처리를 다르게 주는 것이 괜찮을 것 같다!


첫 번째 개선

home(information: Information): Observable<any> {
const url = this.urlConfig.getURL();
const settingData$ = from(this.memberConfig.getValue().then(uuid => {
return {
information: information,
type: 'show'
} as HttpDataType;
}));
return settingData$.pipe(
tap(_ => { console.log(`settingData$`) }),
mergeMap(
(data: HttpDataType) => this.httpClient.post(url, data)
),
retry(3),
catchError(error => {
console.log(`error is ${JSON.stringify(error)} error.status is ${error.status}`);
if (error.status === 503) {
return of(error.status);
}
return Observable.throw(`status: ${error.status} message: ${error.message}`);
})
);
}
view raw first.ts hosted with ❤ by GitHub

개선한 것

- 서버에 보낼 최종 데이터를 세팅하는 부분을 provider로 옮겼습니다.  
※   getValue()로 얻는 값은 provider를 사용하는 모든 곳에서 별도로 받아서 옮기고 파라미터로 받아왔습니다. 중복을 피하기 위해 provider 내에서 한 번만 세팅할 수 있도록 했습니다.   
getValue() ← 포스팅을 위해 변경한 이름  
- 흐름(Stream)을 이어나갔습니다.   
※ 위에 언급한 getValue()는 Promise 함수입니다. 따라서 데이터가 제대로 세팅될 수 있도록 then() 이후에 서버에 보낼 데이터를 세팅하고, 세팅한 값을 return 했습니다. HTTP통신에 이용하기 위하여, Promise를 Observable로 변경하기 위해 from() operator로 묶었습니다.    
- 개선하려고 했던 내용(위에서 말한 생각들)을 개선했습니다.
retry() operator로 서버통신에 실패했을 경우에 대한 처리를 보완했습니다.   

mergeMap(): settingData$에서 받은 데이터를 받아 통신에 보내기 위해 사용
retry(): 통신에 실패했을 경우 3번 다시 시도하도록 함
catchError(): error.status에 따라 처리를 다르게 해주기 위해 넣음. 503이 아닌 경우는 재시도 없이 끝내도록 함

개선했지만 부족해보인다..

- 시간을 두고 재시도하는 게 좋을 것 같다.   
※ retry() operator 통신에러가 발생하면 지체 없이 바로 재시도를 했습니다. 서버 지연의 경우 시간을 두고 재시도하는 것이 더 적합할 것 같았습니다. (시간 지나고 재시도했을 때 될 수도 있으니!)
- 503도 에러는 맞다.
※ error.status가 503인 경우는 에러를 던지지 않게 짰지만, 생각해보면 재시도했는데 계속 503이면 이것도 이용이 불가능한 상태이기 때문에 에러를 내보내 줘야 한다고 생각했습니다.

 


두 번째 개선

home(information: Information): Observable<any> {
const url = this.urlConfig.getURL();
const settingData$ = from(this.memberConfig.getValue().then(uuid => {
return {
information: information,
type: 'show'
} as HttpDataType;
}));
let countRetry = 1;
return settingData$.pipe(
mergeMap((data: HttpDataType) => this.httpClient.post(url, data)),
retryWhen(error$ => error$.pipe(
delay(1500),
take(4),
tap(error => {
const log = `login retryError status: ${error.status} message: ${error.message}`;
if (error.status !== 503) { // 503은 일시적인 현상이므로 재시도한다.
console.log(`error status is not 503 error.status is ${error.status}`);
throw log;
}
countRetry++;
if (countRetry === 5) {
console.log(`countRetry complete`);
throw log;
}
console.log(`after take!! countRetry :${countRetry}`);
})
))
);
}
view raw second.ts hosted with ❤ by GitHub

개선한 것

- retry() operator retryWhen() operator로 변경했습니다.   
retry() operator는 단순히 재시도가 가능했지만 retryWhen() operator는 더 유연했습니다. retryWhen은 subscription을 받아서 다시 제어할 수 있었습니다. 따라서 더 유연한 작업을 위해 변경했습니다.   
- 지연시간을 추가했습니다.  
※ 첫 번째 개선 때 고민하던 부분을 해소하기 위해 delay() operator를 이용하여 take() operator로 통신을 재시도하기 전에 지연시간을 주었습니다.   
- 모든 시도마다 통신을 실패했을 때, 에러를 던지는 것으로 변경했습니다.   
take() operator로 재시도를 하지만, 설정한 재시도가 끝났을 경우 어떻게 처리할지 고민했습니다. tap() operator 매 재시도 때마다 실행되는 곳이기 때문에 이 함수를 응용했습니다. retryWhen() operator 에러가 발생했을 경우에만 들어오는 점을 함께 생각했습니다.   
 countRetry라는 변수를 두고 tap() 안에서 카운팅 되도록 했습니다. 모든 재시도가 끝났는데도 에러라면 if (countRetry ===5) 조건으로 들어옵니다. 이 경우에 tap()으로 전달받은 데이터를 throw 시켜서 subscribeonError()로 잡히도록 했습니다. 

retryWhen(): 좀 더 유연한 통신 재시도를 위해 사용 [흐름 자체를 파라미터로 받음]
delay(): 시간을 지연시키기 위해 사용 [*delay(1500) : 1.5초 지연]
take(): 원하는 만큼 다시 실행하기 위해서 사용 [*take(4) : 4번 재시도]
tap(): 흐름을 제어하기 위해서 사용 [ex.503이 아닌 경우 재시도 안 하고 끝내기]

포스팅에 사용한 함수명, 변수명은 포스팅용입니다. 실무에 사용한 이름은 아닙니다.
"어떻게" RxJS를 활용했는지에 대해서 봐주시면 됩니다! 

 

읽어주셔서 감사합니다 :)  

 

내용에 잘못된 부분이 있다면 댓글 부탁드립니다!


참고 사이트

https://angular.io/guide/http

 

Angular

 

angular.io

https://han41858.tistory.com/39

 

Angular v.4.3.0에 추가된 HttpClientModule

2017년 7월 10일에 발표된 Angular v.4.3.0 버전에 HttpClientModule이 추가되었습니다. Angular에는 원래 @angular/http 패키지로 제공하던 HttpModule이 있었지만, 이번에 추가된 HttpClientModule을 사용하면..

han41858.tistory.com

https://stackoverflow.com/questions/44979131/rxjs-retry-with-delay-function

 

Rxjs Retry with Delay function

I am trying to use retry with delay function, I expect function will call after 1000ms delay, but it doesnot, what can be error here? look at console output, it is same time 16:22:48. I expect th...

stackoverflow.com

 

'programming > RxJS' 카테고리의 다른 글

toPromise? firstValueFrom? lastValueFrom?  (0) 2023.02.04
forkjoin 마이그레이션  (0) 2021.07.09
fromEvent()  (0) 2019.07.20
데이터 가공해서 출력하기 (pluck operator)  (0) 2019.07.12