dispatch 비동기 처리 순서 보장 문제와 해결 방법을 체계적으로 정리한 문서를 작성했습니다. ## 작성된 문서 1. README.md - 전체 개요 및 목차 2. 01-problem.md - 문제 상황 및 원인 분석 3. 02-solution-dispatch-helper.md - dispatchHelper.js 솔루션 4. 03-solution-async-utils.md - asyncActionUtils.js 솔루션 5. 04-solution-queue-system.md - 큐 기반 패널 액션 시스템 6. 05-usage-patterns.md - 사용 패턴 및 실전 예제 ## 주요 내용 ### 문제 - Redux-thunk에서 여러 dispatch 순서가 보장되지 않는 문제 - setTimeout(fn, 0) 임시방편의 한계 ### 해결 방법 1. **dispatchHelper.js** (2025-11-05) - createSequentialDispatch: Promise 체인 기반 순차 실행 - createApiThunkWithChain: API 후 dispatch 체이닝 - withLoadingState: 로딩 상태 자동 관리 2. **asyncActionUtils.js** (2025-11-06) - 성공 기준 명확화: HTTP 200-299 + retCode 0/'0' - reject 없이 resolve만 사용하여 Promise 체인 보장 - 타임아웃 지원 3. **큐 기반 패널 액션 시스템** (2025-11-06) - queuedPanelActions.js: 패널 액션 큐 - panelQueueMiddleware.js: 자동 큐 처리 - 비동기 액션 순차 실행 ## 관련 커밋 -9490d72[251105] feat: dispatchHelper.js -5bd2774[251106] feat: Queued Panel functions -f9290a1[251106] fix: Dispatch Queue implementation
211 lines
5.5 KiB
Markdown
211 lines
5.5 KiB
Markdown
# 문제 상황: Dispatch 비동기 순서 미보장
|
|
|
|
## 🔴 핵심 문제
|
|
|
|
Redux-thunk는 비동기 액션을 지원하지만, **여러 개의 dispatch를 순차적으로 호출할 때 실행 순서가 보장되지 않습니다.**
|
|
|
|
## 📝 기존 코드의 문제점
|
|
|
|
### 예제 1: homeActions.js
|
|
|
|
**파일**: `src/actions/homeActions.js`
|
|
|
|
```javascript
|
|
export const getHomeTerms = (props) => (dispatch, getState) => {
|
|
const onSuccess = (response) => {
|
|
if (response.data.retCode === 0) {
|
|
// 첫 번째 dispatch
|
|
dispatch({
|
|
type: types.GET_HOME_TERMS,
|
|
payload: response.data,
|
|
});
|
|
|
|
// 두 번째 dispatch
|
|
dispatch({
|
|
type: types.SET_TERMS_ID_MAP,
|
|
payload: termsIdMap,
|
|
});
|
|
|
|
// ⚠️ 문제: setTimeout으로 순서 보장 시도
|
|
setTimeout(() => {
|
|
dispatch(getTermsAgreeYn());
|
|
}, 0);
|
|
}
|
|
};
|
|
|
|
TAxios(dispatch, getState, "get", URLS.GET_HOME_TERMS, ..., onSuccess, onFail);
|
|
};
|
|
```
|
|
|
|
**문제점**:
|
|
1. `setTimeout(fn, 0)`은 임시방편일 뿐, 명확한 해결책이 아님
|
|
2. 코드 가독성이 떨어짐
|
|
3. 타이밍 이슈로 인한 버그 가능성
|
|
4. 유지보수가 어려움
|
|
|
|
### 예제 2: cartActions.js
|
|
|
|
**파일**: `src/actions/cartActions.js`
|
|
|
|
```javascript
|
|
export const addToCart = (props) => (dispatch, getState) => {
|
|
const onSuccess = (response) => {
|
|
// 첫 번째 dispatch: 카트에 추가
|
|
dispatch({
|
|
type: types.ADD_TO_CART,
|
|
payload: response.data.data,
|
|
});
|
|
|
|
// 두 번째 dispatch: 카트 정보 재조회
|
|
// ⚠️ 문제: 순서가 보장되지 않음
|
|
dispatch(getMyInfoCartSearch({ mbrNo }));
|
|
};
|
|
|
|
TAxios(dispatch, getState, "post", URLS.ADD_TO_CART, ..., onSuccess, onFail);
|
|
};
|
|
```
|
|
|
|
**문제점**:
|
|
1. `getMyInfoCartSearch`가 `ADD_TO_CART`보다 먼저 실행될 수 있음
|
|
2. 카트 정보가 업데이트되기 전에 재조회가 실행될 수 있음
|
|
3. 순서가 보장되지 않아 UI에 잘못된 데이터가 표시될 수 있음
|
|
|
|
## 🤔 왜 순서가 보장되지 않을까?
|
|
|
|
### Redux-thunk의 동작 방식
|
|
|
|
```javascript
|
|
// Redux-thunk는 이렇게 동작합니다
|
|
function dispatch(action) {
|
|
if (typeof action === 'function') {
|
|
// thunk action인 경우
|
|
return action(dispatch, getState);
|
|
} else {
|
|
// plain action인 경우
|
|
return next(action);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 문제 시나리오
|
|
|
|
```javascript
|
|
// 이렇게 작성하면
|
|
dispatch({ type: 'ACTION_1' }); // Plain action - 즉시 실행
|
|
dispatch(asyncAction()); // Thunk - 비동기 실행
|
|
dispatch({ type: 'ACTION_2' }); // Plain action - 즉시 실행
|
|
|
|
// 실제 실행 순서는
|
|
// 1. ACTION_1 (동기)
|
|
// 2. ACTION_2 (동기)
|
|
// 3. asyncAction의 내부 dispatch들 (비동기)
|
|
|
|
// 즉, asyncAction이 완료되기 전에 ACTION_2가 실행됩니다!
|
|
```
|
|
|
|
## 🎯 해결해야 할 과제
|
|
|
|
1. **순서 보장**: 여러 dispatch가 의도한 순서대로 실행되도록
|
|
2. **에러 처리**: 중간에 에러가 발생해도 체인이 끊기지 않도록
|
|
3. **가독성**: 코드가 직관적이고 유지보수하기 쉽도록
|
|
4. **재사용성**: 여러 곳에서 쉽게 사용할 수 있도록
|
|
5. **호환성**: 기존 코드와 호환되도록
|
|
|
|
## 📊 실제 발생 가능한 버그
|
|
|
|
### 시나리오 1: 카트 추가 후 조회
|
|
|
|
```javascript
|
|
// 의도한 순서
|
|
1. ADD_TO_CART dispatch
|
|
2. 상태 업데이트
|
|
3. getMyInfoCartSearch dispatch
|
|
4. 최신 카트 정보 조회
|
|
|
|
// 실제 실행 순서 (문제)
|
|
1. ADD_TO_CART dispatch
|
|
2. getMyInfoCartSearch dispatch (너무 빨리 실행!)
|
|
3. 이전 카트 정보 조회 (아직 상태 업데이트 안됨)
|
|
4. 상태 업데이트
|
|
→ 결과: UI에 이전 데이터가 표시됨
|
|
```
|
|
|
|
### 시나리오 2: 패널 열고 닫기
|
|
|
|
```javascript
|
|
// 의도한 순서
|
|
1. PUSH_PANEL (검색 패널 열기)
|
|
2. UPDATE_PANEL (검색 결과 표시)
|
|
3. POP_PANEL (이전 패널 닫기)
|
|
|
|
// 실제 실행 순서 (문제)
|
|
1. PUSH_PANEL
|
|
2. POP_PANEL (너무 빨리 실행!)
|
|
3. UPDATE_PANEL (이미 닫힌 패널을 업데이트)
|
|
→ 결과: 패널이 제대로 표시되지 않음
|
|
```
|
|
|
|
## 🔧 기존 해결 방법과 한계
|
|
|
|
### 방법 1: setTimeout 사용
|
|
|
|
```javascript
|
|
dispatch(action1());
|
|
setTimeout(() => {
|
|
dispatch(action2());
|
|
}, 0);
|
|
```
|
|
|
|
**한계**:
|
|
- 명확한 순서 보장 없음
|
|
- 타이밍에 의존적
|
|
- 코드 가독성 저하
|
|
- 유지보수 어려움
|
|
|
|
### 방법 2: 콜백 중첩
|
|
|
|
```javascript
|
|
const action1 = (callback) => (dispatch, getState) => {
|
|
dispatch({ type: 'ACTION_1' });
|
|
if (callback) callback();
|
|
};
|
|
|
|
dispatch(action1(() => {
|
|
dispatch(action2(() => {
|
|
dispatch(action3());
|
|
}));
|
|
}));
|
|
```
|
|
|
|
**한계**:
|
|
- 콜백 지옥
|
|
- 에러 처리 복잡
|
|
- 코드 가독성 최악
|
|
|
|
### 방법 3: async/await
|
|
|
|
```javascript
|
|
export const complexAction = () => async (dispatch, getState) => {
|
|
await dispatch(action1());
|
|
await dispatch(action2());
|
|
await dispatch(action3());
|
|
};
|
|
```
|
|
|
|
**한계**:
|
|
- Chrome 68 호환성 문제 (프로젝트 요구사항)
|
|
- 모든 action이 Promise를 반환해야 함
|
|
- 기존 코드 대량 수정 필요
|
|
|
|
## 🎯 다음 단계
|
|
|
|
이제 이러한 문제들을 해결하기 위한 3가지 솔루션을 살펴보겠습니다:
|
|
|
|
1. [dispatchHelper.js](./02-solution-dispatch-helper.md) - Promise 체인 기반 헬퍼 함수
|
|
2. [asyncActionUtils.js](./03-solution-async-utils.md) - Promise 기반 비동기 처리 유틸리티
|
|
3. [큐 기반 패널 액션 시스템](./04-solution-queue-system.md) - 미들웨어 기반 큐 시스템
|
|
|
|
---
|
|
|
|
**다음**: [해결 방법 1: dispatchHelper.js →](./02-solution-dispatch-helper.md)
|