# Promise 제대로 사용하는 법 — 동기값 반환 때문에 발생하는 .then 오류와 해결

## 들어가며

JavaScript에서 비동기 작업을 다룰 때 Promise를 일관되게 반환하는 것이 중요합니다.

함수가 Promise 대신 동기값(또는 undefined)을 반환하면 호출부에서 .then/.catch를 호출할 수 없어 런타임 오류가 납니다.

문제 사례 (실제 오류)

* 에러: Uncaught TypeError: User.isDuplicateLogin(...).then is not a function
    
* 원인: isDuplicateLogin이 Promise를 반환하지 않고 boolean 또는 undefined를 반환했기 때문.
    

나쁜 예 (동기 boolean 반환 — 오류 발생)

```javascript
// 잘못된 패턴: 동기값을 반환하면 호출부에서 .then을 쓸 수 없음
function isDuplicateLogin(userId) {
    // 서버 호출을 했지만 결과를 반환(return)하지 않음
    axiosApi.get(`/api/user/${userId}/auth`).then((res) => {
        return !!res?.results?.hasActiveSessions; // 이 값은 then 핸들러 내부에서만 사용되고 함수는 undefined 반환
    });
}
```

올바른 예: Promise를 반환하도록 고치기

* 핵심: 내부에서의 비동기 체인을 반드시 return 하여 호출자에게 Promise를 돌려준다.
    

```javascript
// 권장: 항상 Promise<boolean>을 반환
function isDuplicateLogin(userId) {
    return axiosApi
        .get(`/api/user/${encodeURIComponent(userId)}/auth`)
        .then((res) => {
            if (!res || typeof res.results?.hasActiveSessions === 'undefined') {
                // 서버 응답이 기대치와 다르면 에러를 던져 호출자가 판단하게 함
                const e = new Error('NO_DUPLICATE_INFO');
                e.code = 'NO_DUPLICATE_INFO';
                throw e;
            }
            // boolean으로 표준화해서 반환
            return !!res.results.hasActiveSessions;
        });
}
```

호출부 예시 1 — Promise 체인 (.then/.catch/.finally)

```javascript
// 로그인 성공 후 중복확인 흐름 (async/await 없이)
User.isDuplicateLogin(userId)
    .then(function (isDup) {
        if (isDup) {
            const proceed = confirm('다른 세션이 있습니다. 계속하시겠습니까?');
            if (!proceed) throw { handled: true }; // 체인 중단 신호
        }
        // 중복 검사를 통과한 이후 처리
        // 상태 설정, 로컬 정리, 리다이렉트 등
    })
    .catch(function (err) {
        if (err && err.handled) return;
        if (err?.code === 'NO_DUPLICATE_INFO') {
            alert('서버에서 중복 여부를 확인할 수 없습니다.');
            return;
        }
        // 실제 오류 처리
        console.error(err);
        alert('중복 확인 중 오류가 발생했습니다.');
    })
    .finally(function () {
        // 로딩 숨김/플래그 복구는 항상 finally에서
        utilHideLoadingIndicator();
        User.isLoginPending = false;
    });
```

호출부 예시 2 — async/await 스타일

```javascript
async function handleLoginFlow(userId) {
    try {
        const isDup = await User.isDuplicateLogin(userId);
        if (isDup) {
            const proceed = confirm('다른 세션이 있습니다. 계속하시겠습니까?');
            if (!proceed) return;
        }
        // 이후 처리
    } catch (err) {
        if (err?.code === 'NO_DUPLICATE_INFO') {
            alert('서버에서 중복 여부를 확인할 수 없습니다.');
            return;
        }
        if (err?.name === 'CanceledError' || err?.code === 'ERR_CANCELED') return;
        console.error(err);
        alert('중복 확인 중 오류가 발생했습니다.');
    } finally {
        utilHideLoadingIndicator();
        User.isLoginPending = false;
    }
}
```

### 이번에 배운점!

* 항상 Promise를 반환(return)하라. (axios/Fetch는 Promise, jQuery의 $.ajax는 jqXHR(then 사용 가능)임을 유의)
    
* 함수는 결과를 boolean/도메인 값으로 표준화해 반환하거나, 오류는 던져(callee에서 throw) 호출자가 판단하도록 하라.
    
* 에러를 덮어쓰지 말고 필요한 경우 코드/메시지를 붙여 throw하라(구분용).
    
* 상태 복구(utilHideLoadingIndicator, isLoginPending 등)는 finally에서 처리해 레이스 컨디션을 막아라.
    
* 취소(AbortController)와 같은 케이스는 호출부와 인터셉터에서 일관되게 무시하도록 처리하라.
    

결론

* Promise를 제대로 반환하고 에러 전파/정규화를 명확히 하면 .then is not a function 같은 오류를 예방할 수 있다.
    
* 호출부는 반환된 Promise를 기다리거나 catch에서 적절한 처리를 하면 안전한 비동기 흐름을 만들 수 있다.
