비동기 = 순서가 보장되지 않은 상황
대표적인 비동기 상황
서버에 요청을 하면 응답이 올 때 까지 기다리고 다른 유저 인터랙션에 반응할 수 없는 것이 아닌, 서버에서 응답이 올 때 까지 다른 작업을 하면서 응답이 오면 다시 이어서 할 일을 하는 것이다.
따라서 끊기지 않는 60프레임*의 좋은 사용자 경험을 위해서는 비동기 프로그래밍은 필수이다.
웹 브라우저 렌더링은 60프레임이다.
https://365kim.tistory.com/152
https://ordinary-code.tistory.com/58
좋은 코드란 무엇인지 예제를 통해 알아본 뒤 비동기 처리에 적용해보자
JS에 Promise가 없던 시절
JS에 Promise가 없었을 때의 코드를 보면 callback함수를 매개변수에 넣어서 callback을 구현한 것을 볼 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
});
});
}
위 예제 코드의 문제점
성공과 실패하는 경우가 같이 있어서 함수의 역할이 가려진다. 또 코드를 작성하는 입장에서 매번 비동기 호출을 할 때 마다 에러 처리를 해줘야해서 불편하다.
위의 코드를 보고 async-await로 구현된 코드를 보면 async-await의 좋은 점을 알 수 있다.
1
2
3
4
5
async function fetchAccounts(){
const user = await fetchUserEntity();
const accounts = await fetchUserAccounts(user.no);
return accounts;
}
‘성공하는 경우’만 다루고 ‘실패하는 경우’는 catch절에서 분리해서 처리한다. 덕분에 함수의 역할이 잘 드러난다.
‘실패하는 경우’에 대한 처리를 외부에 위임할 수 있다.
이를 통해 좋은 코드의 특징을 정리할 수 있다.
좋은 코드 특징 |
---|
성공/실패하는 경우를 분리해서 처리 가능 |
비즈니스 로직을 한눈에 파악이 가능 |
함수에 성공하는 경우만 적혀있어서 읽기도 쉽고 함수의 책임이 명확히 드러난다.
비동기 코드가 2개 이상일 때
비동기는 ‘로딩’, ‘에러’, ‘완료’ 총 3가지 상태가 존재한다.
예를 들어 2개의 비동기 리소스가 있다고 할 때 리소스를 가져올 떄의 상태를 나타낸 표가 있다.
로딩 | 에러 | 완료 | |
---|---|---|---|
로딩 | 🔺 | ❌ | 🔺 |
에러 | ❌ | ❌ | ❌ |
완료 | 🔺 | ❌ | ✅ |
그래서 비동기 리소스가 2개만 있다고 해도 총 9개의 상태를 가질 수 있다.
그런데 이러한 비동기 호출이 3, 4개면 더 복잡해질 것이다.
React의 비동기 처리는 어렵다.
React의 Hook이나 State를 사용하는 방식으로는 async-await와 같은 간단한 비동기 구현이 어렵다.
하지만 리액트 팀이 제안하는 “React Suspense for Data Fetching”이 있다.
말 그래도 “데이터를 가져오기 위한 Suspense” 이다. 취지는 async-await처럼 비동기 처리가 가능한 React 컴포넌트 구현이다.
성공한 경우와 로딩/에러의 경우를 나눠서 처리하면 에러/로딩 상태는 어떻게 처리할까? 함수의 에러처리를 감싸는 catch문에서 하는 것처럼 로딩상태와 에러처리도 컴포넌트를 쓰는 곳에서 해준다. 에러를 담당하는 <ErrorBoundary>
, 로딩을 담당하는 <Suspense>
가 있다. <runPureTask>
라는 런타임은 비동기 함수도 동기적으로 작성할 수 있다.
후기
React를 설명하는 부분은 내가 react를 잘 몰라서 3번을 돌려봤을때도 잘 이해하기 어려웠다. 사실 Javascript callback 구문도 이해가 잘 안됐다.