# ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• 2: asyncActionUtils.js ## ๐Ÿ“ฆ ๊ฐœ์š” **ํŒŒ์ผ**: `src/utils/asyncActionUtils.js` **์ž‘์„ฑ์ผ**: 2025-11-06 **์ปค๋ฐ‹**: `f9290a1 [251106] fix: Dispatch Queue implementation` Promise ๊ธฐ๋ฐ˜์˜ ๋น„๋™๊ธฐ ์•ก์…˜ ์ฒ˜๋ฆฌ์™€ **์ƒ์„ธํ•œ ์„ฑ๊ณต/์‹คํŒจ ๊ธฐ์ค€**์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ## ๐ŸŽฏ ํ•ต์‹ฌ ๊ฐœ๋… ### ํ”„๋กœ์ ํŠธ ํŠนํ™” ์„ฑ๊ณต ๊ธฐ์ค€ ์ด ํ”„๋กœ์ ํŠธ์—์„œ API ํ˜ธ์ถœ ์„ฑ๊ณต์€ **2๊ฐ€์ง€ ์กฐ๊ฑด**์„ ๋ชจ๋‘ ๋งŒ์กฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: 1. โœ… **HTTP ์ƒํƒœ ์ฝ”๋“œ**: 200-299 ๋ฒ”์œ„ 2. โœ… **retCode**: 0 ๋˜๋Š” '0' ```javascript // HTTP 200์ด์ง€๋งŒ retCode๊ฐ€ 1์ธ ๊ฒฝ์šฐ { status: 200, // โœ… HTTP๋Š” ์„ฑ๊ณต data: { retCode: 1, // โŒ retCode๋Š” ์‹คํŒจ message: "๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค" } } // โ†’ ์ด๊ฒƒ์€ ์‹คํŒจ์ž…๋‹ˆ๋‹ค! ``` ### Promise ์ฒด์ธ์ด ๋Š๊ธฐ์ง€ ์•Š๋Š” ์„ค๊ณ„ **ํ•ต์‹ฌ ์›์น™**: ๋ชจ๋“  ๋น„๋™๊ธฐ ์ž‘์—…์€ **reject ์—†์ด resolve๋งŒ ์‚ฌ์šฉ**ํ•ฉ๋‹ˆ๋‹ค. ```javascript // โŒ ์ผ๋ฐ˜์ ์ธ ๋ฐฉ์‹ (Promise ์ฒด์ธ์ด ๋Š๊น€) return new Promise((resolve, reject) => { if (error) { reject(error); // ์ฒด์ธ์ด ๋Š๊น€! } }); // โœ… ์ด ํ”„๋กœ์ ํŠธ์˜ ๋ฐฉ์‹ (์ฒด์ธ ์œ ์ง€) return new Promise((resolve) => { if (error) { resolve({ success: false, error: { code: 'ERROR', message: '์—๋Ÿฌ ๋ฐœ์ƒ' } }); } }); ``` --- ## ๐Ÿ”‘ ํ•ต์‹ฌ ํ•จ์ˆ˜ 1. `isApiSuccess` - API ์„ฑ๊ณต ์—ฌ๋ถ€ ํŒ๋‹จ 2. `fetchApi` - Promise ๊ธฐ๋ฐ˜ fetch ๋ž˜ํผ 3. `tAxiosToPromise` - TAxios๋ฅผ Promise๋กœ ๋ณ€ํ™˜ 4. `wrapAsyncAction` - ๋น„๋™๊ธฐ ์•ก์…˜์„ Promise๋กœ ๋ž˜ํ•‘ 5. `withTimeout` - ํƒ€์ž„์•„์›ƒ ์ง€์› 6. `executeParallelAsyncActions` - ๋ณ‘๋ ฌ ์‹คํ–‰ --- ## 1๏ธโƒฃ isApiSuccess ### ์„ค๋ช… API ์‘๋‹ต์ด ์„ฑ๊ณต์ธ์ง€ ํŒ๋‹จํ•˜๋Š” **ํ”„๋กœ์ ํŠธ ํ‘œ์ค€ ํ•จ์ˆ˜**์ž…๋‹ˆ๋‹ค. ### ๊ตฌํ˜„ **ํŒŒ์ผ**: `src/utils/asyncActionUtils.js:21-34` ```javascript export const isApiSuccess = (response, responseData) => { // 1๏ธโƒฃ HTTP ์ƒํƒœ ์ฝ”๋“œ ํ™•์ธ (200-299 ์„ฑ๊ณต ๋ฒ”์œ„) if (!response.ok || response.status < 200 || response.status >= 300) { return false; } // 2๏ธโƒฃ retCode ํ™•์ธ - 0 ๋˜๋Š” '0'์ด์–ด์•ผ ์„ฑ๊ณต if (responseData && responseData.retCode !== undefined) { return responseData.retCode === 0 || responseData.retCode === '0'; } // retCode๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ HTTP ์ƒํƒœ ์ฝ”๋“œ๋งŒ์œผ๋กœ ํŒ๋‹จ return response.ok; }; ``` ### ์‚ฌ์šฉ ์˜ˆ์ œ ```javascript // ์„ฑ๊ณต ์ผ€์ด์Šค isApiSuccess( { ok: true, status: 200 }, { retCode: 0, data: { ... } } ); // โ†’ true isApiSuccess( { ok: true, status: 200 }, { retCode: '0', data: { ... } } ); // โ†’ true // ์‹คํŒจ ์ผ€์ด์Šค isApiSuccess( { ok: true, status: 200 }, { retCode: 1, message: "๊ถŒํ•œ ์—†์Œ" } ); // โ†’ false (retCode๊ฐ€ 0์ด ์•„๋‹˜) isApiSuccess( { ok: false, status: 500 }, { retCode: 0, data: { ... } } ); // โ†’ false (HTTP ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 500) isApiSuccess( { ok: false, status: 404 }, { retCode: 0 } ); // โ†’ false (404 ์—๋Ÿฌ) ``` --- ## 2๏ธโƒฃ fetchApi ### ์„ค๋ช… **ํ‘œ์ค€ fetch API๋ฅผ Promise๋กœ ๋ž˜ํ•‘**ํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ์„ฑ๊ณต ๊ธฐ์ค€์— ๋งž์ถฐ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ### ํ•ต์‹ฌ ํŠน์ง• - โœ… ํ•ญ์ƒ `resolve` ์‚ฌ์šฉ (reject ์—†์Œ) - โœ… HTTP ์ƒํƒœ + retCode ๋ชจ๋‘ ํ™•์ธ - โœ… JSON ํŒŒ์‹ฑ ์—๋Ÿฌ๋„ ์ฒ˜๋ฆฌ - โœ… ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ๋„ ์ฒ˜๋ฆฌ - โœ… ์ƒ์„ธํ•œ ๋กœ๊น… ### ๊ตฌํ˜„ **ํŒŒ์ผ**: `src/utils/asyncActionUtils.js:57-123` ```javascript export const fetchApi = (url, options = {}) => { console.log('[asyncActionUtils] ๐ŸŒ FETCH_API_START', { url, method: options.method || 'GET' }); return new Promise((resolve) => { // โš ๏ธ ํ•ญ์ƒ resolve๋งŒ ์‚ฌ์šฉ! fetch(url, { headers: { 'Content-Type': 'application/json', ...options.headers }, ...options }) .then(response => { // JSON ํŒŒ์‹ฑ return response.json() .then(responseData => { console.log('[asyncActionUtils] ๐Ÿ“Š API_RESPONSE', { status: response.status, ok: response.ok, retCode: responseData.retCode, success: isApiSuccess(response, responseData) }); // โœ… ์„ฑ๊ณต/์‹คํŒจ ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด ํ•ญ์ƒ resolve resolve({ response, data: responseData, success: isApiSuccess(response, responseData), error: !isApiSuccess(response, responseData) ? { code: responseData.retCode || response.status, message: responseData.message || getApiErrorMessage(responseData.retCode || response.status), httpStatus: response.status } : null }); }) .catch(parseError => { console.error('[asyncActionUtils] โŒ JSON_PARSE_ERROR', parseError); // โœ… JSON ํŒŒ์‹ฑ ์‹คํŒจ๋„ resolve๋กœ ์ฒ˜๋ฆฌ resolve({ response, data: null, success: false, error: { code: 'PARSE_ERROR', message: '์‘๋‹ต ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค', originalError: parseError } }); }); }) .catch(error => { console.error('[asyncActionUtils] ๐Ÿ’ฅ FETCH_ERROR', error); // โœ… ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ ๋“ฑ๋„ resolve๋กœ ์ฒ˜๋ฆฌ resolve({ response: null, data: null, success: false, error: { code: 'NETWORK_ERROR', message: error.message || '๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค', originalError: error } }); }); }); }; ``` ### ์‚ฌ์šฉ ์˜ˆ์ œ ```javascript import { fetchApi } from '../utils/asyncActionUtils'; // ๊ธฐ๋ณธ ์‚ฌ์šฉ const result = await fetchApi('/api/products/123', { method: 'GET' }); if (result.success) { console.log('์„ฑ๊ณต:', result.data); // HTTP 200-299 + retCode 0/'0' } else { console.error('์‹คํŒจ:', result.error); // error.code, error.message ์‚ฌ์šฉ ๊ฐ€๋Šฅ } // POST ์š”์ฒญ const result = await fetchApi('/api/cart', { method: 'POST', body: JSON.stringify({ productId: 123 }) }); // ํ—ค๋” ์ถ”๊ฐ€ const result = await fetchApi('/api/user', { method: 'GET', headers: { 'Authorization': 'Bearer token123' } }); ``` ### ๋ฐ˜ํ™˜ ๊ตฌ์กฐ ```javascript // ์„ฑ๊ณต ์‹œ { response: Response, // fetch Response ๊ฐ์ฒด data: { ... }, // ํŒŒ์‹ฑ๋œ JSON ๋ฐ์ดํ„ฐ success: true, // ์„ฑ๊ณต ํ”Œ๋ž˜๊ทธ error: null // ์—๋Ÿฌ ์—†์Œ } // ์‹คํŒจ ์‹œ (HTTP ์—๋Ÿฌ) { response: Response, data: { retCode: 1, message: "๊ถŒํ•œ ์—†์Œ" }, success: false, error: { code: 1, message: "๊ถŒํ•œ ์—†์Œ", httpStatus: 200 } } // ์‹คํŒจ ์‹œ (๋„คํŠธ์›Œํฌ ์—๋Ÿฌ) { response: null, data: null, success: false, error: { code: 'NETWORK_ERROR', message: '๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค', originalError: Error } } ``` --- ## 3๏ธโƒฃ tAxiosToPromise ### ์„ค๋ช… ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” **TAxios๋ฅผ Promise๋กœ ๋ณ€ํ™˜**ํ•ฉ๋‹ˆ๋‹ค. ### ๊ตฌํ˜„ **ํŒŒ์ผ**: `src/utils/asyncActionUtils.js:138-204` ```javascript export const tAxiosToPromise = ( TAxios, dispatch, getState, method, baseUrl, urlParams, params, options = {} ) => { return new Promise((resolve) => { console.log('[asyncActionUtils] ๐Ÿ”„ TAXIOS_TO_PROMISE_START', { method, baseUrl }); const enhancedOnSuccess = (response) => { console.log('[asyncActionUtils] โœ… TAXIOS_SUCCESS', { retCode: response?.data?.retCode }); // TAxios ์„ฑ๊ณต ์ฝœ๋ฐฑ๋„ ์„ฑ๊ณต ๊ธฐ์ค€ ์ ์šฉ const isSuccess = response?.data && ( response.data.retCode === 0 || response.data.retCode === '0' ); resolve({ response, data: response.data, success: isSuccess, error: !isSuccess ? { code: response.data?.retCode || 'UNKNOWN_ERROR', message: response.data?.message || getApiErrorMessage(response.data?.retCode || 'UNKNOWN_ERROR') } : null }); }; const enhancedOnFail = (error) => { console.error('[asyncActionUtils] โŒ TAXIOS_FAIL', error); resolve({ // โš ๏ธ reject๊ฐ€ ์•„๋‹Œ resolve response: null, data: null, success: false, error: { code: error.retCode || 'TAXIOS_ERROR', message: error.message || 'API ํ˜ธ์ถœ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค', originalError: error } }); }; try { TAxios( dispatch, getState, method, baseUrl, urlParams, params, enhancedOnSuccess, enhancedOnFail, options.noTokenRefresh || false, options.responseType ); } catch (error) { console.error('[asyncActionUtils] ๐Ÿ’ฅ TAXIOS_EXECUTION_ERROR', error); resolve({ response: null, data: null, success: false, error: { code: 'EXECUTION_ERROR', message: 'API ํ˜ธ์ถœ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค', originalError: error } }); } }); }; ``` ### ์‚ฌ์šฉ ์˜ˆ์ œ ```javascript import { tAxiosToPromise } from '../utils/asyncActionUtils'; import { TAxios } from '../utils/TAxios'; export const getProductDetail = (productId) => async (dispatch, getState) => { const result = await tAxiosToPromise( TAxios, dispatch, getState, 'get', URLS.GET_PRODUCT_DETAIL, {}, { productId }, {} ); if (result.success) { dispatch({ type: types.GET_PRODUCT_DETAIL, payload: result.data.data }); } else { console.error('์ƒํ’ˆ ์กฐํšŒ ์‹คํŒจ:', result.error); } }; ``` --- ## 4๏ธโƒฃ wrapAsyncAction ### ์„ค๋ช… ๋น„๋™๊ธฐ ์•ก์…˜ ํ•จ์ˆ˜๋ฅผ Promise๋กœ ๋ž˜ํ•‘ํ•˜์—ฌ **ํ‘œ์ค€ํ™”๋œ ๊ฒฐ๊ณผ ๊ตฌ์กฐ**๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ### ๊ตฌํ˜„ **ํŒŒ์ผ**: `src/utils/asyncActionUtils.js:215-270` ```javascript export const wrapAsyncAction = (asyncAction, context = {}) => { return new Promise((resolve) => { const { dispatch, getState } = context; console.log('[asyncActionUtils] ๐ŸŽฏ WRAP_ASYNC_ACTION_START'); // ์„ฑ๊ณต ์ฝœ๋ฐฑ - ํ•ญ์ƒ resolve ํ˜ธ์ถœ const onSuccess = (result) => { console.log('[asyncActionUtils] โœ… WRAP_ASYNC_SUCCESS', result); resolve({ response: result.response || result, data: result.data || result, success: true, error: null }); }; // ์‹คํŒจ ์ฝœ๋ฐฑ - ํ•ญ์ƒ resolve ํ˜ธ์ถœ (reject ํ•˜์ง€ ์•Š์Œ) const onFail = (error) => { console.error('[asyncActionUtils] โŒ WRAP_ASYNC_FAIL', error); resolve({ response: null, data: null, success: false, error: { code: error.retCode || error.code || 'ASYNC_ACTION_ERROR', message: error.message || error.errorMessage || '๋น„๋™๊ธฐ ์ž‘์—…์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค', originalError: error } }); }; try { // ๋น„๋™๊ธฐ ์•ก์…˜ ์‹คํ–‰ const result = asyncAction(dispatch, getState, onSuccess, onFail); // Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์ฒ˜๋ฆฌ if (result && typeof result.then === 'function') { result .then(onSuccess) .catch(onFail); } } catch (error) { console.error('[asyncActionUtils] ๐Ÿ’ฅ WRAP_ASYNC_EXECUTION_ERROR', error); onFail(error); } }); }; ``` ### ์‚ฌ์šฉ ์˜ˆ์ œ ```javascript import { wrapAsyncAction } from '../utils/asyncActionUtils'; // ๋น„๋™๊ธฐ ์•ก์…˜ ์ •์˜ const myAsyncAction = (dispatch, getState, onSuccess, onFail) => { TAxios(dispatch, getState, 'get', URL, {}, {}, onSuccess, onFail); }; // Promise๋กœ ๋ž˜ํ•‘ํ•˜์—ฌ ์‚ฌ์šฉ const result = await wrapAsyncAction(myAsyncAction, { dispatch, getState }); if (result.success) { console.log('์„ฑ๊ณต:', result.data); } else { console.error('์‹คํŒจ:', result.error.message); } ``` --- ## 5๏ธโƒฃ withTimeout ### ์„ค๋ช… Promise์— **ํƒ€์ž„์•„์›ƒ**์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. ### ๊ตฌํ˜„ **ํŒŒ์ผ**: `src/utils/asyncActionUtils.js:354-373` ```javascript export const withTimeout = ( promise, timeoutMs, timeoutMessage = '์ž‘์—… ์‹œ๊ฐ„์ด ์ดˆ๊ณผ๋˜์—ˆ์Šต๋‹ˆ๋‹ค' ) => { return Promise.race([ promise, new Promise((resolve) => { setTimeout(() => { console.error('[asyncActionUtils] โฐ PROMISE_TIMEOUT', { timeoutMs }); resolve({ response: null, data: null, success: false, error: { code: 'TIMEOUT', message: timeoutMessage, timeout: timeoutMs } }); }, timeoutMs); }) ]); }; ``` ### ์‚ฌ์šฉ ์˜ˆ์ œ ```javascript import { withTimeout, fetchApi } from '../utils/asyncActionUtils'; // 5์ดˆ ํƒ€์ž„์•„์›ƒ const result = await withTimeout( fetchApi('/api/slow-endpoint'), 5000, '์š”์ฒญ์ด ์‹œ๊ฐ„์ดˆ๊ณผ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค' ); if (result.success) { console.log('์„ฑ๊ณต:', result.data); } else if (result.error.code === 'TIMEOUT') { console.error('ํƒ€์ž„์•„์›ƒ ๋ฐœ์ƒ'); } else { console.error('๊ธฐํƒ€ ์—๋Ÿฌ:', result.error); } ``` --- ## 6๏ธโƒฃ executeParallelAsyncActions ### ์„ค๋ช… ์—ฌ๋Ÿฌ ๋น„๋™๊ธฐ ์•ก์…˜์„ **๋ณ‘๋ ฌ๋กœ ์‹คํ–‰**ํ•˜๊ณ  ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. ### ๊ตฌํ˜„ **ํŒŒ์ผ**: `src/utils/asyncActionUtils.js:279-299` ```javascript export const executeParallelAsyncActions = (asyncActions, context = {}) => { console.log('[asyncActionUtils] ๐Ÿš€ EXECUTE_PARALLEL_START', { count: asyncActions.length }); const promises = asyncActions.map(action => wrapAsyncAction(action, context) ); return Promise.all(promises) .then(results => { console.log('[asyncActionUtils] โœ… EXECUTE_PARALLEL_SUCCESS', { successCount: results.filter(r => r.success).length, failCount: results.filter(r => !r.success).length }); return results; }) .catch(error => { console.error('[asyncActionUtils] โŒ EXECUTE_PARALLEL_ERROR', error); return []; }); }; ``` ### ์‚ฌ์šฉ ์˜ˆ์ œ ```javascript import { executeParallelAsyncActions } from '../utils/asyncActionUtils'; // 3๊ฐœ์˜ API๋ฅผ ๋™์‹œ์— ํ˜ธ์ถœ const results = await executeParallelAsyncActions([ (dispatch, getState, onSuccess, onFail) => { TAxios(dispatch, getState, 'get', URL1, {}, {}, onSuccess, onFail); }, (dispatch, getState, onSuccess, onFail) => { TAxios(dispatch, getState, 'get', URL2, {}, {}, onSuccess, onFail); }, (dispatch, getState, onSuccess, onFail) => { TAxios(dispatch, getState, 'get', URL3, {}, {}, onSuccess, onFail); } ], { dispatch, getState }); // ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ results.forEach((result, index) => { if (result.success) { console.log(`API ${index + 1} ์„ฑ๊ณต:`, result.data); } else { console.error(`API ${index + 1} ์‹คํŒจ:`, result.error); } }); ``` --- ## ๐Ÿ“Š ์‹ค์ œ ์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค ### ์‹œ๋‚˜๋ฆฌ์˜ค 1: API ํ˜ธ์ถœ ํ›„ ํ›„์† ์ฒ˜๋ฆฌ ```javascript import { tAxiosToPromise } from '../utils/asyncActionUtils'; export const addToCartAndRefresh = (productId) => async (dispatch, getState) => { // 1. ์นดํŠธ์— ์ถ”๊ฐ€ const addResult = await tAxiosToPromise( TAxios, dispatch, getState, 'post', URLS.ADD_TO_CART, {}, { productId }, {} ); if (addResult.success) { // 2. ์นดํŠธ ์ถ”๊ฐ€ ์„ฑ๊ณต ์‹œ ์นดํŠธ ์ •๋ณด ์žฌ์กฐํšŒ dispatch({ type: types.ADD_TO_CART, payload: addResult.data.data }); const cartResult = await tAxiosToPromise( TAxios, dispatch, getState, 'get', URLS.GET_CART, {}, { mbrNo: addResult.data.data.mbrNo }, {} ); if (cartResult.success) { dispatch({ type: types.GET_CART, payload: cartResult.data.data }); } } else { console.error('์นดํŠธ ์ถ”๊ฐ€ ์‹คํŒจ:', addResult.error); } }; ``` ### ์‹œ๋‚˜๋ฆฌ์˜ค 2: ํƒ€์ž„์•„์›ƒ์ด ์žˆ๋Š” API ํ˜ธ์ถœ ```javascript import { tAxiosToPromise, withTimeout } from '../utils/asyncActionUtils'; export const getLargeData = () => async (dispatch, getState) => { const result = await withTimeout( tAxiosToPromise( TAxios, dispatch, getState, 'get', URLS.GET_LARGE_DATA, {}, {}, {} ), 10000, // 10์ดˆ ํƒ€์ž„์•„์›ƒ '๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹œ๊ฐ„์ด ์ดˆ๊ณผ๋˜์—ˆ์Šต๋‹ˆ๋‹ค' ); if (result.success) { dispatch({ type: types.GET_LARGE_DATA, payload: result.data.data }); } else if (result.error.code === 'TIMEOUT') { // ํƒ€์ž„์•„์›ƒ ์ฒ˜๋ฆฌ dispatch({ type: types.SHOW_TIMEOUT_MESSAGE }); } else { // ๊ธฐํƒ€ ์—๋Ÿฌ ์ฒ˜๋ฆฌ console.error('์กฐํšŒ ์‹คํŒจ:', result.error); } }; ``` --- ## โœ… ์žฅ์  1. **์„ฑ๊ณต ๊ธฐ์ค€ ๋ช…ํ™•ํ™”**: HTTP + retCode ๋ชจ๋‘ ํ™•์ธ 2. **์ฒด์ธ ๋ณด์žฅ**: reject ์—†์ด resolve๋งŒ ์‚ฌ์šฉํ•˜์—ฌ Promise ์ฒด์ธ ์œ ์ง€ 3. **์ƒ์„ธํ•œ ๋กœ๊น…**: ๋ชจ๋“  ๋‹จ๊ณ„์—์„œ ๋กœ๊ทธ ์ถœ๋ ฅ 4. **ํƒ€์ž„์•„์›ƒ ์ง€์›**: ์‘๋‹ต ์—†๋Š” API ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ 5. **์—๋Ÿฌ ์ฒ˜๋ฆฌ**: ๋ชจ๋“  ์—๋Ÿฌ๋ฅผ ํ‘œ์ค€ ๊ตฌ์กฐ๋กœ ๋ฐ˜ํ™˜ ## โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ 1. **Chrome 68 ํ˜ธํ™˜**: async/await ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์ฃผ์˜ ํ•„์š” 2. **ํ•ญ์ƒ resolve**: reject ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ 3. **success ํ”Œ๋ž˜๊ทธ**: ๋ฐ˜๋“œ์‹œ `result.success` ํ™•์ธ ํ•„์š” --- **๋‹ค์Œ**: [ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• 3: ํ ๊ธฐ๋ฐ˜ ํŒจ๋„ ์•ก์…˜ ์‹œ์Šคํ…œ โ†’](./04-solution-queue-system.md)