13. 시간을 다루는 마법 (비동기 처리)/13.3 약속을 지키는 Promise

13.3.3 Promise 체이닝 - 비동기 작업들을 순서대로 연결하기

thejavascript4kids 2025. 7. 19. 04:52

📘 13.3.3 Promise 체이닝 - 비동기 작업들을 순서대로 연결하기

여러분과 함께 지나온 시간들이 쌓여서, 이제 Promise의 더 깊은 아름다움을 함께 발견할 때가 왔습니다. 지난 시간에 Promise의 then, catch, finally를 배웠다면, 오늘은 여러 개의 비동기 작업을 순서대로 연결하는 Promise 체이닝이라는 섬세한 기법을 배워보겠습니다.

마치 봄이 여름으로, 여름이 가을로 자연스럽게 이어지듯, 하나의 작업이 끝나면 자동으로 다음 작업이 시작되는 흐름. 그 흐름 속에서 우리는 복잡한 일들을 차근차근 해결해 나갈 수 있습니다.

🧠 새로운 단어들과 친해지기

단어 쉬운 설명
Promise 체이닝 여러 Promise를 사슬처럼 연결해서 순서대로 실행하는 방법이에요
체인 (Chain) 고리고리 연결된 사슬처럼, 코드가 하나씩 이어져서 실행되는 구조예요
순서대로 실행 첫 번째 작업이 완전히 끝나야 두 번째 작업이 시작되는 방식이에요
중간 결과 체이닝 과정에서 각 단계마다 나오는 결과값들을 말해요

✨ Promise 체이닝의 핵심 개념

Promise 체이닝은 여러 개의 비동기 작업을 순서대로 실행하고, 각 단계의 결과를 다음 단계로 전달하는 강력한 기법입니다. 콜백 지옥의 근본적인 해결책이며, 현대 자바스크립트에서 비동기 처리의 표준 방법이 되었습니다.

체이닝의 핵심 원리는 then 메서드가 새로운 Promise를 반환한다는 것입니다. 이 특성 덕분에 .then().then().then() 형태로 계속 연결할 수 있습니다.

가장 중요한 규칙: 각 then에서 반드시 return을 해야 합니다! return을 하지 않으면 다음 then에서 undefined를 받게 되어 원하는 대로 작동하지 않습니다.

따뜻한 비유: 음식 만들기 과정 이야기

어머니가 정성스럽게 볶음밥을 만들 때는 여러 단계를 거칩니다. "재료 준비하기 → 밥 짓기 → 야채 썰기 → 재료 볶기 → 간 맞추기 → 완성" 이런 순서로 조용히 진행되지요.

각 단계는 이전 단계가 완료되어야만 시작할 수 있습니다. 밥이 지어지지 않으면 볶을 수 없고, 야채가 썰어지지 않으면 볶을 수 없지요. 또한 각 단계에서는 이전 단계의 결과물을 받아서 자신의 작업을 한 다음, 완성된 결과물을 다음 단계로 조용히 넘겨줍니다.

Promise 체이닝도 이 요리 과정과 같은 마음으로 이루어집니다.

🎯 Promise 체이닝을 사용하는 이유

  1. 콜백 지옥의 완전한 해결: 중첩 구조를 평면적인 구조로 바꿔줍니다
  2. 코드 읽기 쉬움: "첫 번째로 이것을 하고, 그다음에 저것을 하고" 순서를 쉽게 이해할 수 있습니다
  3. 데이터 흐름이 명확: 각 단계에서 어떤 데이터를 받고 처리하는지 명확합니다
  4. 오류 처리 간소화: 마지막 catch 하나로 모든 오류를 처리할 수 있습니다

⚙️ 기본 사용법 살펴보기

기본 체이닝 구조:

firstPromise()
    .then(function(첫번째결과) {
        // 첫 번째 결과를 처리하고 다음 Promise나 값을 반환
        return secondPromise(첫번째결과);
    })
    .then(function(두번째결과) {
        // 두 번째 결과를 처리하고 다음 Promise나 값을 반환
        return thirdPromise(두번째결과);
    })
    .then(function(세번째결과) {
        // 최종 결과 처리
        console.log("모든 작업 완료:", 세번째결과);
    })
    .catch(function(오류) {
        // 어느 단계에서든 발생한 오류를 처리
        console.log("오류 발생:", 오류);
    });

체이닝의 핵심 규칙:

  • 각 then에서는 반드시 값이나 Promise를 return 해야 합니다
  • return한 값이 다음 then의 매개변수가 됩니다
  • Promise를 return하면 그 Promise가 완료될 때까지 다음 then이 기다립니다

🧪 예제로 익혀보기

🔹 예제 1: 기본적인 3단계 체이닝

// 1단계: 기본 데이터 준비
function prepareData() {
    return new Promise(function(resolve) {
        console.log("📋 1단계: 기본 데이터를 준비해요...");
        setTimeout(function() {
            resolve("기본데이터");
        }, 1000);
    });
}

// 2단계: 데이터 변환
function transformData(data) {
    return new Promise(function(resolve) {
        console.log("🔄 2단계: 데이터를 변환해요 -", data);
        setTimeout(function() {
            resolve(data + "→변환됨");
        }, 1000);
    });
}

// 3단계: 최종 처리
function finalizeData(data) {
    return new Promise(function(resolve) {
        console.log("✅ 3단계: 최종 처리해요 -", data);
        setTimeout(function() {
            resolve(data + "→완료");
        }, 1000);
    });
}

// Promise 체이닝으로 실행
prepareData()
    .then(function(step1Result) {
        console.log("1단계 결과:", step1Result);
        return transformData(step1Result);
    })
    .then(function(step2Result) {
        console.log("2단계 결과:", step2Result);
        return finalizeData(step2Result);
    })
    .then(function(finalResult) {
        console.log("🎉 모든 처리 완료:", finalResult);
    })
    .catch(function(error) {
        console.log("❌ 어딘가에서 문제 발생:", error);
    });

🔹 예제 2: 사용자 로그인 후 정보 가져오기 체이닝

// 로그인 처리
function loginUser(username, password) {
    return new Promise(function(resolve, reject) {
        console.log("🔑 로그인 시도:", username);
        setTimeout(function() {
            if (username === "student" && password === "1234") {
                resolve({ username: username, token: "abc123" });
            } else {
                reject("로그인 실패: 아이디 또는 비밀번호가 틀렸어요");
            }
        }, 1000);
    });
}

// 사용자 정보 가져오기
function fetchUserProfile(loginInfo) {
    return new Promise(function(resolve) {
        console.log("📋 사용자 정보 가져오는 중... 토큰:", loginInfo.token);
        setTimeout(function() {
            resolve({
                username: loginInfo.username,
                name: "김학생",
                grade: "5학년",
                class: "3반"
            });
        }, 1000);
    });
}

// 권한 확인
function checkPermissions(userProfile) {
    return new Promise(function(resolve) {
        console.log("🛡️ 권한 확인 중...", userProfile.name + "님");
        setTimeout(function() {
            resolve({
                userInfo: userProfile,
                permissions: ["읽기", "쓰기", "댓글달기"],
                accessLevel: "학생"
            });
        }, 1000);
    });
}

// 전체 로그인 과정을 체이닝으로 처리
loginUser("student", "1234")
    .then(function(loginResult) {
        console.log("✅ 로그인 성공:", loginResult.username);
        return fetchUserProfile(loginResult);
    })
    .then(function(profileResult) {
        console.log("✅ 프로필 조회 완료:", profileResult.name);
        return checkPermissions(profileResult);
    })
    .then(function(finalResult) {
        console.log("🎉 모든 인증 과정 완료!");
        console.log("사용자:", finalResult.userInfo.name);
        console.log("권한:", finalResult.permissions.join(", "));
        console.log("메인 페이지로 이동해요!");
    })
    .catch(function(error) {
        console.log("❌ 인증 과정에서 문제:", error);
        console.log("로그인 페이지로 돌아가요.");
    });

🔹 예제 3: 값과 Promise를 섞어서 사용하는 체이닝

// 간단한 계산 함수들
function multiplyByTwo(number) {
    return new Promise(function(resolve) {
        console.log("🔢 2를 곱해요:", number + " × 2");
        setTimeout(function() {
            resolve(number * 2);
        }, 500);
    });
}

function addTen(number) {
    // 일반 값을 반환 (자동으로 Promise.resolve()로 변환돼요)
    console.log("➕ 10을 더해요:", number + " + 10");
    return number + 10;
}

function divideByThree(number) {
    return new Promise(function(resolve) {
        console.log("➗ 3으로 나눠요:", number + " ÷ 3");
        setTimeout(function() {
            resolve(Math.floor(number / 3));
        }, 500);
    });
}

// 숫자 5부터 시작해서 여러 계산을 체이닝으로 처리
Promise.resolve(5)
    .then(function(startNumber) {
        console.log("🎯 시작 숫자:", startNumber);
        return multiplyByTwo(startNumber); // 5 × 2 = 10
    })
    .then(function(result1) {
        console.log("1단계 결과:", result1);
        return addTen(result1); // 10 + 10 = 20
    })
    .then(function(result2) {
        console.log("2단계 결과:", result2);
        return divideByThree(result2); // 20 ÷ 3 = 6
    })
    .then(function(finalResult) {
        console.log("🎉 최종 계산 결과:", finalResult);
        console.log("계산 과정: 5 → 10 → 20 → 6");
    })
    .catch(function(error) {
        console.log("❌ 계산 중 문제:", error);
    });

🔄 Promise 체이닝 실행 과정 정리하기

  1. 첫 번째 Promise 시작: 체이닝의 시작점이 되는 Promise나 값 실행
  2. 첫 번째 then 실행: 첫 번째 Promise 완료 후 결과를 첫 번째 then으로 전달
  3. 체이닝 계속: 각 then이 이전 결과를 받아서 처리하고 다음으로 전달
  4. 오류 발생 시 catch로 이동: 어느 단계에서든 오류 발생 시 즉시 catch 실행
  5. 완료 또는 오류 처리: 모든 단계 성공 시 마지막 then, 오류 시 catch에서 처리

🧠 자주 하는 실수와 주의할 점

❌ 실수 1: then에서 Promise를 반환하지 않아서 체이닝이 끊어지는 경우

// 잘못된 예시 - return을 빼먹음
firstStep()
    .then(function(result1) {
        console.log("1단계 완료:", result1);
        secondStep(result1); // return을 하지 않았어요!
    })
    .then(function(result2) {
        console.log("2단계 결과:", result2); // undefined가 출력돼요
    });

// 올바른 예시
firstStep()
    .then(function(result1) {
        console.log("1단계 완료:", result1);
        return secondStep(result1); // return 필수!
    })
    .then(function(result2) {
        console.log("2단계 결과:", result2); // 올바른 결과 출력
    });

❌ 실수 2: Promise 안에서 또 다른 Promise를 중첩해서 콜백 지옥 만들기

// 나쁜 예시 - Promise 안에 Promise 중첩
firstStep()
    .then(function(result1) {
        secondStep(result1)
            .then(function(result2) {
                thirdStep(result2)
                    .then(function(result3) {
                        console.log("최종 결과:", result3);
                    });
            });
    });

// 좋은 예시 - 평면적인 체이닝
firstStep()
    .then(function(result1) {
        return secondStep(result1);
    })
    .then(function(result2) {
        return thirdStep(result2);
    })
    .then(function(result3) {
        console.log("최종 결과:", result3);
    });

❌ 실수 3: 각 단계마다 오류 처리를 따로 해서 비효율적인 코드 만들기

// 비효율적인 예시 - 각 단계마다 오류 처리
firstStep()
    .then(function(result1) {
        return secondStep(result1);
    })
    .catch(function(error1) {
        console.log("1단계 오류:", error1);
    })
    .then(function(result2) {
        return thirdStep(result2);
    })
    .catch(function(error2) {
        console.log("2단계 오류:", error2);
    });

// 효율적인 예시 - 마지막에 통합 오류 처리
firstStep()
    .then(function(result1) {
        return secondStep(result1);
    })
    .then(function(result2) {
        return thirdStep(result2);
    })
    .then(function(result3) {
        console.log("모든 단계 완료:", result3);
    })
    .catch(function(error) {
        console.log("어느 단계에서든 오류 발생:", error);
    });

✏️ 연습문제로 개념 다지기

연습을 시작하기 전에 잠시 숨을 고르겠습니다. Promise 체이닝이라는 것이 단순히 기술적인 방법이 아니라, 우리가 일상에서 자연스럽게 하는 일들과 닮아 있다는 걸 느껴보셨나요. 하나의 일이 끝나면 그 결과를 바탕으로 다음 일을 시작하는 것. 그 과정에서 우리는 차근차근 원하는 목표에 다가갑니다.

Ex1) 간단한 3단계 작업을 Promise 체이닝으로 연결해보자

// 각 단계별 함수들
function step1() {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve("1단계 완료");
        }, 1000);
    });
}

function step2(data) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(data + " → 2단계 완료");
        }, 1000);
    });
}

function step3(data) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(data + " → 3단계 완료");
        }, 1000);
    });
}

// Promise 체이닝으로 연결
step1()
    .then(function(result1) {
        console.log("첫 번째:", result1);
        return step2(result1);
    })
    .then(function(result2) {
        console.log("두 번째:", result2);
        return step3(result2);
    })
    .then(function(result3) {
        console.log("최종:", result3);
    })
    .catch(function(error) {
        console.log("오류:", error);
    });

Ex2) 숫자 계산을 순서대로 처리하는 체이닝

function addFive(number) {
    return Promise.resolve(number + 5);
}

function multiplyByTwo(number) {
    return new Promise(function(resolve) {
        resolve(number * 2);
    });
}

function subtractThree(number) {
    return Promise.resolve(number - 3);
}

// 10부터 시작해서 계산 체이닝
Promise.resolve(10)
    .then(addFive)           // 10 + 5 = 15
    .then(multiplyByTwo)     // 15 * 2 = 30  
    .then(subtractThree)     // 30 - 3 = 27
    .then(function(result) {
        console.log("계산 결과:", result); // 27
    })
    .catch(function(error) {
        console.log("계산 오류:", error);
    });

🤔 심화 문제로 실력 확인하기

Q1. Promise 체이닝에서 가장 중요한 규칙은 무엇이고, 왜 중요한가요?

정답: then 메서드에서 다음 Promise나 값을 반드시 return해야 한다는 것입니다.

해설: return을 하지 않으면 다음 then에서 undefined를 받게 되어 체이닝이 제대로 작동하지 않습니다. return은 각 단계의 결과를 다음 단계로 전달하는 유일한 방법입니다.

Q2. Promise 체이닝이 콜백 지옥보다 좋은 이유 세 가지는?

정답:

  1. 코드가 평면적으로 쓰여져서 읽기 쉽습니다
  2. 오류 처리를 마지막에 catch 하나로 통합할 수 있습니다
  3. 각 단계를 독립적인 함수로 분리해서 재사용하고 테스트하기 쉽습니다

Q3. 다음 체이닝의 최종 결과를 예측해보세요.

Promise.resolve(3)
    .then(function(num) {
        return num * 4;
    })
    .then(function(num) {
        return num + 8;
    })
    .then(function(num) {
        return num / 2;
    })
    .then(function(result) {
        console.log("최종 결과:", result);
    });

정답: "최종 결과: 10"이 출력됩니다.

해설: 3 → 12 (3×4) → 20 (12+8) → 10 (20÷2) 순서로 계산됩니다.

🔙 지난 시간 복습하기 (13.3.2 - then, catch, finally)

복습 문제 1: then, catch, finally 중 결과값을 받지 않는 것은?

정답: finally

복습 문제 2: 다음 코드의 출력 순서는?

console.log("1");
Promise.resolve("성공")
    .then(function(result) {
        console.log("2:", result);
    });
console.log("3");

정답: "1" → "3" → "2: 성공"

Promise 체이닝은 복잡한 비동기 작업을 차분하고 이해하기 쉬운 코드로 만들어주는 강력한 도구입니다. 다음 시간에는 여러 Promise를 동시에 처리하는 방법들을 배워보겠습니다.

✅ 학습 완료 체크리스트

학습 내용 이해했나요?
Promise 체이닝의 기본 개념
return의 중요성
기본 사용법과 체이닝
자주 하는 실수들
실전 예제 만들기

🎯 추가 연습 문제들

여러분이 더 깊이 탐구하고 싶어 하는 마음이 전해집니다. 추가 연습문제들을 통해 그 마음에 응답하겠습니다.

추가 문제 1. 세 개의 Promise를 순서대로 연결하는 체이닝

function task1() {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve("작업1 완료");
        }, 1000);
    });
}

function task2(data) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(data + " → 작업2 완료");
        }, 1000);
    });
}

function task3(data) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(data + " → 작업3 완료");
        }, 1000);
    });
}

task1()
    .then(task2)
    .then(task3)
    .then(function(result) {
        console.log("최종:", result);
    })
    .catch(function(error) {
        console.log("에러:", error);
    });

추가 문제 2. 다음 코드의 문제점을 찾고 수정해보세요

// 문제 코드
step1()
    .then(function(result) {
        step2(result); // return이 없어요!
    })
    .then(function(result) {
        console.log("결과:", result);
    });

// 수정된 코드
step1()
    .then(function(result) {
        return step2(result); // return 추가!
    })
    .then(function(result) {
        console.log("결과:", result);
    });

📂 마무리 정보

오늘 배운 Promise 체이닝이 여러분의 자바스크립트 지식에 자연스럽게 스며들었나요? 다음 시간에는 더 효율적인 비동기 처리 방법들을 배워볼 예정입니다.


🚀 더 체계적인 JavaScript 학습을 원하신다면?
이 포스팅에서 다룬 내용을 실제로 실습해보세요!
무료 JavaScript 학습 플랫폼에서 단계별 학습과 실시간 코드 실행을 통해
더욱 효과적이고 재미있게 학습하실 수 있습니다.
📝 실시간 코드 실행 📊 학습 진도 관리 👥 체계적 커리큘럼
📚 171개 체계적 학습레슨 · 📋 855개 4지선다 연습문제 · 🆓 완전 무료 · ⚡ 즉시 시작