# 문제 상황: 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)