Files
shoptime/.docs/dispatch-async/01-problem.md
Claude f75860c8dd [문서화] Dispatch 비동기 처리 순서 보장 솔루션 문서 작성
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
2025-11-10 09:26:40 +00:00

5.5 KiB

문제 상황: Dispatch 비동기 순서 미보장

🔴 핵심 문제

Redux-thunk는 비동기 액션을 지원하지만, 여러 개의 dispatch를 순차적으로 호출할 때 실행 순서가 보장되지 않습니다.

📝 기존 코드의 문제점

예제 1: homeActions.js

파일: src/actions/homeActions.js

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

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. getMyInfoCartSearchADD_TO_CART보다 먼저 실행될 수 있음
  2. 카트 정보가 업데이트되기 전에 재조회가 실행될 수 있음
  3. 순서가 보장되지 않아 UI에 잘못된 데이터가 표시될 수 있음

🤔 왜 순서가 보장되지 않을까?

Redux-thunk의 동작 방식

// Redux-thunk는 이렇게 동작합니다
function dispatch(action) {
  if (typeof action === 'function') {
    // thunk action인 경우
    return action(dispatch, getState);
  } else {
    // plain action인 경우
    return next(action);
  }
}

문제 시나리오

// 이렇게 작성하면
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: 카트 추가 후 조회

// 의도한 순서
1. ADD_TO_CART dispatch
2. 상태 업데이트
3. getMyInfoCartSearch dispatch
4. 최신 카트 정보 조회

// 실제 실행 순서 (문제)
1. ADD_TO_CART dispatch
2. getMyInfoCartSearch dispatch (너무 빨리 실행!)
3. 이전 카트 정보 조회 (아직 상태 업데이트 안됨)
4. 상태 업데이트
 결과: UI에 이전 데이터가 표시됨

시나리오 2: 패널 열고 닫기

// 의도한 순서
1. PUSH_PANEL (검색 패널 열기)
2. UPDATE_PANEL (검색 결과 표시)
3. POP_PANEL (이전 패널 닫기)

// 실제 실행 순서 (문제)
1. PUSH_PANEL
2. POP_PANEL (너무 빨리 실행!)
3. UPDATE_PANEL (이미 닫힌 패널을 업데이트)
 결과: 패널이 제대로 표시되지 않음

🔧 기존 해결 방법과 한계

방법 1: setTimeout 사용

dispatch(action1());
setTimeout(() => {
  dispatch(action2());
}, 0);

한계:

  • 명확한 순서 보장 없음
  • 타이밍에 의존적
  • 코드 가독성 저하
  • 유지보수 어려움

방법 2: 콜백 중첩

const action1 = (callback) => (dispatch, getState) => {
  dispatch({ type: 'ACTION_1' });
  if (callback) callback();
};

dispatch(action1(() => {
  dispatch(action2(() => {
    dispatch(action3());
  }));
}));

한계:

  • 콜백 지옥
  • 에러 처리 복잡
  • 코드 가독성 최악

방법 3: async/await

export const complexAction = () => async (dispatch, getState) => {
  await dispatch(action1());
  await dispatch(action2());
  await dispatch(action3());
};

한계:

  • Chrome 68 호환성 문제 (프로젝트 요구사항)
  • 모든 action이 Promise를 반환해야 함
  • 기존 코드 대량 수정 필요

🎯 다음 단계

이제 이러한 문제들을 해결하기 위한 3가지 솔루션을 살펴보겠습니다:

  1. dispatchHelper.js - Promise 체인 기반 헬퍼 함수
  2. asyncActionUtils.js - Promise 기반 비동기 처리 유틸리티
  3. 큐 기반 패널 액션 시스템 - 미들웨어 기반 큐 시스템

다음: 해결 방법 1: dispatchHelper.js →