JavaScript 개발 중 'Uncaught TypeError: .then is not a function' 오류에 직면하셨나요? 이 오류는 Promise 객체가 아닌 값에 .then() 메소드를 호출하려 할 때 발생하며, 비동기 프로그래밍에서 매우 흔하게 나타나는 문제입니다. 주로 Promise 처리 방식을 잘못 이해하거나 구현했을 때 발생하는데요, 이 가이드에서는 오류의 주요 원인을 깊이 있게 분석하고, 명확한 코드 예제와 함께 효과적인 해결 방법을 상세히 알아보겠습니다.
1. 오류의 주요 원인 분석
.then is not a function 오류가 발생하는 가장 일반적인 시나리오들은 다음과 같습니다.
- Promise를 반환하지 않는 함수에
.then()사용:.then()메소드는 오직 Promise 객체에만 사용할 수 있습니다. 함수가 Promise를 반환하지 않는데도 체이닝하려고 할 때 이 오류가 발생합니다. async함수 내에서await키워드 누락:async함수는 암묵적으로 Promise를 반환합니다. 그러나async함수 내부에서 비동기 연산의 결과를 기다리기 위해await키워드를 사용하지 않으면, 해당 비동기 연산이 완료되기 전의 Promise 객체(또는 다른 값)가 반환되어.then()체이닝에 문제를 일으킬 수 있습니다.- Promise 객체가 아닌 값을 반환하는 함수 체이닝: Promise 체인의 중간 단계에서 Promise가 아닌 일반 값을 반환하는 경우, 그 다음
.then()호출에서 오류가 발생할 수 있습니다. - 동기 함수를 비동기처럼 다루려는 시도: 본질적으로 동기적인 함수가 Promise를 반환할 것으로 오해하고
.then()을 붙일 때 발생합니다. - 라이브러리나 API의 잘못된 사용: 특정 라이브러리나 웹 API가 예상과 다른 값을 반환하거나, 비동기 처리에 대한 사용법을 잘못 적용했을 때 발생할 수 있습니다.
2. 효과적인 해결 방법
2.1. 함수가 Promise를 반환하는지 확인
.then()을 호출하는 함수가 실제로 Promise 객체를 반환하는지 확인하는 것이 가장 중요합니다. Promise를 반환하지 않는 함수라면, 명시적으로 Promise로 감싸서 반환해야 합니다.
// ❌ 잘못된 예 (Promise를 반환하지 않음)
function getData() {
console.log("데이터를 가져오는 중...");
return { status: 'success', value: 'some data' }; // 일반 객체 반환
}
// ⬇️ 오류 발생: getData()는 Promise가 아니므로 .then()을 사용할 수 없습니다.
// getData().then(result => console.log("결과:", result));
// ✅ 수정된 예 (Promise.resolve()로 Promise 반환)
function getPromisedData() {
console.log("Promise 데이터를 가져오는 중...");
// Promise.resolve()를 사용하여 일반 값을 Promise로 감쌉니다.
return Promise.resolve({ status: 'success', value: 'some data' });
}
// ➡️ 정상 작동: getPromisedData()는 Promise를 반환하므로 .then()을 사용할 수 있습니다.
getPromisedData().then(result => console.log("Promise 결과:", result));
2.2. async/await 키워드 올바른 사용
async 함수 내에서 비동기 연산의 결과를 기다릴 때는 반드시 await 키워드를 사용해야 합니다. await을 누락하면 Promise가 완료되기 전의 상태로 넘어가서 문제가 발생할 수 있습니다.
// ❌ 잘못된 예 (await 누락)
async function fetchBadData() {
// await이 없으면 fetch 함수가 반환하는 Promise 객체 자체가 아닌,
// Promise 객체가 resolve되기 전의 상태(pending Promise)를 response 변수에 할당합니다.
const response = fetch('https://jsonplaceholder.typicode.com/posts/1');
// 여기서 response는 여전히 Promise 객체이므로, response.json()은 Promise의 메소드가 아님!
// -> response.json() 대신 Promise.prototype.json()을 호출하려 시도하게 됩니다.
const data = await response.json(); // "response.json is not a function" 오류 발생 가능
console.log(data);
}
// ⬇️ fetchBadData().then(...)도 오류를 유발할 수 있습니다.
// fetchBadData().then(data => console.log(data)).catch(err => console.error("오류:", err));
// ✅ 수정된 예 (await 올바른 사용)
async function fetchGoodData() {
try {
// fetch 호출 앞에 await을 붙여 Promise가 완료될 때까지 기다립니다.
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
// response는 이제 실제 Response 객체이므로 .json() 메소드를 가집니다.
const data = await response.json(); // 여기서도 Promise를 기다림
console.log("정상 데이터:", data);
} catch (error) {
console.error('데이터 가져오기 오류:', error);
}
}
// ➡️ 정상 작동: async 함수는 항상 Promise를 반환하므로 .then()을 사용할 수 있습니다.
fetchGoodData().then(() => console.log("데이터 가져오기 완료."));
2.3. Promise 체이닝 로직 수정
Promise 체인에서 각 .then() 핸들러가 다음 .then()으로 전달될 Promise를 올바르게 반환하는지 확인해야 합니다. 만약 중간 핸들러가 Promise가 아닌 값을 반환하면, 다음 .then() 호출에서 오류가 발생합니다.
function step1() {
return Promise.resolve(10); // Promise 반환
}
function step2(num) {
console.log("Step 2:", num);
return num * 2; // Promise가 아닌 일반 숫자 반환
}
function step3(num) {
console.log("Step 3:", num);
return Promise.resolve(num + 5); // Promise 반환
}
// ❌ 잘못된 Promise 체이닝 (step2가 Promise를 반환하지 않아 다음 .then()에서 오류)
step1()
.then(result1 => step2(result1)) // step2는 Promise를 반환하지 않음
.then(result2 => step3(result2)) // ⬇️ 여기서 'result2.then is not a function' 오류 발생!
.then(finalResult => console.log("최종 결과 (오류 발생):", finalResult))
.catch(error => console.error('체이닝 오류:', error));
// ✅ 수정된 Promise 체이닝 (모든 단계가 Promise를 반환하도록 수정)
function newStep2(num) {
console.log("New Step 2:", num);
return Promise.resolve(num * 2); // Promise.resolve()로 감싸서 Promise 반환
}
step1()
.then(result1 => newStep2(result1)) // newStep2는 Promise를 반환
.then(result2 => step3(result2)) // step3는 Promise를 반환
.then(finalResult => console.log("최종 결과 (정상):", finalResult)) // -> "최종 결과 (정상): 25"
.catch(error => console.error('체이닝 오류:', error));
3. 모범 사례 및 오류 방지 전략
비동기 코드를 작성할 때 .then is not a function 오류를 사전에 방지하기 위한 몇 가지 모범 사례입니다.
- 함수가 Promise를 반환하는지 명확히 문서화: 함수의 JSDoc이나 주석을 통해 해당 함수가 Promise를 반환하는지, 어떤 값을 resolve하는지 명시하여 다른 개발자가 오용하지 않도록 합니다.
async/await와 Promise를 일관성 있게 사용: 프로젝트 내에서 비동기 처리 스타일을 통일하면 혼란을 줄이고 오류 발생 가능성을 낮출 수 있습니다.- 타입스크립트(TypeScript) 사용으로 타입 관련 오류 사전 방지: TypeScript는 컴파일 시점에 타입을 검사하여 Promise
와 같은 타입 명시를 통해 런타임 오류를 줄여줍니다. Promise.resolve()나Promise.reject()로 값을 명시적으로 Promise로 감싸기: 특히 조건부 로직이나 동기적인 값을 비동기 체인에 포함시켜야 할 때 유용합니다.- 복잡한 비동기 로직은
async함수로 분리: 복잡한 Promise 체인 대신async함수를 사용하여 가독성을 높이고try-catch로 오류를 쉽게 관리할 수 있습니다.
4. 디버깅 팁
오류가 발생했을 때 문제를 효과적으로 찾아내기 위한 디버깅 팁입니다.
console.log()를 사용하여 각 단계의 반환값 확인: Promise 체인의 각.then()블록 내부나async함수 내에서console.log()를 사용하여 각 변수나 함수의 반환값이 기대하는 Promise 객체인지, 혹은 다른 값인지 확인합니다.- 브라우저 개발자 도구의 네트워크 탭에서 API 응답 검사: 외부 API를 호출하는 경우, 개발자 도구의 네트워크 탭에서 실제 응답이 정상적으로 오는지, 응답 형식이 올바른지 확인합니다.
- Promise 체인에
.catch()추가하여 오류 지점 파악: Promise 체인 끝에.catch()블록을 추가하여 오류가 발생한 지점을 특정하고, 오류 메시지를 통해 원인을 추적합니다. async/await사용 시try-catch블록으로 오류 처리:async함수 내에서try-catch블록을 사용하여 비동기 작업 중 발생하는 오류를 명확히 포착하고 디버깅합니다.- 타입스크립트 사용 시
Promise타입 명시로 타입 안정성 확보: 함수가 Promise를 반환할 때Promise(예:Promise)와 같이 명시하여 개발 환경에서부터 타입 오류를 미리 발견합니다.
결론
'Uncaught TypeError: .then is not a function' 오류는 JavaScript의 비동기 프로그래밍에서 Promise 객체를 잘못 다룰 때 주로 발생합니다. 이 오류를 효과적으로 방지하고 해결하기 위해서는 Promise와 async/await의 기본 개념을 정확히 이해하고 올바르게 사용하는 것이 중요합니다.
이 가이드에서 제시한 원인 분석, 해결 방법, 모범 사례 및 디버깅 팁을 따르면 대부분의 .then is not a function 오류를 해결할 수 있습니다. 비동기 코드를 작성할 때는 항상 함수의 반환값과 타입을 신중히 고려하고, 필요한 경우 Promise.resolve()나 Promise.reject()를 사용하여 값을 명시적으로 Promise로 감싸는 습관을 들이는 것이 좋습니다.
