프론트엔드 웹서비스에서 우아하게 비동기 처리하기

Haz·2022년 11월 21일
0

개발여행기

목록 보기
3/32
post-thumbnail
post-custom-banner

발표 주제 선정 이유


웹 서비스에서 아직까지 다루기 어려운 부분이 바로 비동기 프로그래밍.

“비동기 프로그래밍”: 순서가 보장되지 않는 상황

해당 스니펫의 문제점은?

function getBazFromX(x) {
	if (x === undefined) {
		return undefined;
	}

	if (x.foo === undefined) {
		return undefined;
	}

	if (x.foo.bar === undefined) {
		return undefined;
	}

	return x.foo.bar.baz;
}

x.foo.bar.baz 프로퍼티에 안전하게 접근하려고 하는 코드

→ 함수가 하는 일은 굉장히 단순하나, 코드가 복잡. 각 프로퍼티에 접근하는 핵심 기능이 코드로 드러나지 않음(명령어에 노이즈가 많아서 명확하게 드러나지 않음).

문제점 개선

function getBazFromX(x) {
	return x?.foo?.bar?.baz;
}

Optional Chaining 문법을 활용한 동일 함수

→ 코드가 간결. ‘성공한 경우’를 생각하는 x.foo.bar.baz와 문법적 차이가 크지 않음. 함수의 역할을 한눈에 파악할 수 있음.



비동기 처리의 어려운 점


비동기 처리를 위한 스니펫의 문제점을 찾아보자

function fetchAccounts(callback){
	fetchUserEntity((err, user) => {
		if(err != null) {
			callback(err, null);
			return;
		}
		
		fetchUserAccounts(user.no, (err, accounts) => {
			if(err != null) {
				callback(err, null);
				return;
			}
			callback(null, accounts);
		});
	});
}

Promise 없던 시절 Callback 비동기 처리 코드

→ ‘성공하는 경우’와 ‘실패하는 경우’가 섞여서 처리됨. 코드를 작성하는 입장에서 매번 에러 유무를 확인해야 함.

문제점 개선

async function fetchAccounts() {
	const user = await fetchUserEntity();
	const accounts = await fetchUserAccounts(user.no);

	return accounts;
}

→ ‘성공하는 경우’만 다루고, ‘실패하는 경우’는 catch 절에서 분리해 처리함. ‘실패하는 경우’에 대한 처리를 외부에 위임할 수 있음.

좋은 코드의 특징

1. 성공, 실패의 경우를 분리해 처리할 수 있다.
2. 비즈니스 로직을 한눈에 파악할 수 있다.


비동기 상태

  • 로딩 중
  • 에러
  • 완료됨

→ 이에 2개의 비동기 리소스를 가져오게 되면 3의 제곱, 즉 9개의 상태를 가지게됨.

리액트에서 비동기 처리하기 어려운 이유

  1. Hook, State를 사용하는 방식으로는 async, await 처럼 간단하게 비동기처리를 할 수 없음
  2. 2가지 이상의 로직이 개입하여 복잡해질 경우 더 처리하기 어려워짐

→ 문제 해결 방법: React Suspense for Data fetching

React Suspense

아직 리액트 자체에서는 실험 단계로 테스트 중이나, 라이브러리를 활용해 이를 구현할 수 있음.

Recoil에서는 비동기 셀렉터로 Suspence를 일으켜, 비동기가 필요한 컴포넌트를 Suspence로 적절히 감싸주기만 하면 됨.

이를 통해 데이터가 준비되는 대로 하나씩 자연스럽게 불러올 수 있음.

React hooks와의 유사도

Hooks: 선언적인 API

  1. useState: 상태 사용을 선언
  2. useMemo: 메모이제이션 사용을 선언
  3. useCallback: 콜백 레퍼런스 보존을 선언
  4. useEffect: 부수 효과 발생을 선언

→ 실제 상태관리, 메모이제이션 등의 작업은 컴포넌트를 감싸는 리액트 프레임워크가 수행

Suspense와 유사한 점

  • 비동기적 리소스에 접근을 선언

→ 실제 로딩 상태, 에러 상태 처리는 컴포넌트를 감싸는 부모 컴포넌트가 수행

에러 처리의 복잡도를 낮춘 방법

1. Suspense

2. try… catch…

실패할 수 있는 함수는 에러를 throw 문으로 발생시킴. 실제 에러 처리는 컴포넌트를 감싸는 부모 함수가 수행

대수적 효과(Algebraic Effects)

  • 함수는 필요한 코드 조각을 선언적으로 사용: useMemo, 비동기 값 읽어오기 등
  • 실제 관련된 처리는 컴포넌트를 감싸는 부모 함수에 위임
  • 이처럼 어떤 코드 조각을 감싸는 맥락으로 책임을 분리하는 방식을 “대수적 효과”라고 일컬음.
  • 객체지향의 의존성 주입, 의존성 역전 과도 유사함

오늘의 궁금한 점

React Concurrent Mode?

useTransition, useDeferredValue?

profile
나도 재밌고, 남들도 재밌는 서비스 만들어보고 싶다😎
post-custom-banner

0개의 댓글