Javascript

[JavaScript] 자바스크립트 | 비동기 처리의 시작 콜백 이해하기, 콜백 지옥 체험 | JavaScript Callback

@leem 2025. 3. 26. 14:28
'use strict';

// JavaScript is synchronous.
// Execute the code block by order after hoisting.
// hoisting: var, function declaration
// (자바스크립트는 기본적으로 동기적이며, 호이스팅 이후 코드 순서대로 실행됨)
// (hoisting: 변수와 함수 선언이 코드 맨 위로 끌어올려지는 현상)
console.log('1');
setTimeout(() => console.log('2'), 1000); 
// (1초 뒤 '2'를 비동기로 출력)
console.log('3'); 
// (먼저 '1', '3' 출력 후, 1초 뒤에 '2' 출력)

// Synchronous callback
// (동기 콜백 함수)
function printImmediately(print) {
  print();
}
printImmediately(() => console.log('hello')); 
// ('hello'가 즉시 출력됨)

// Asynchronous callback
// (비동기 콜백 함수)
function printWithDelay(print, timeout) {
  setTimeout(print, timeout);
}
printWithDelay(() => console.log('async callback'), 2000); 
// (2초 뒤 'async callback' 출력)

// Callback Hell example
// (콜백 지옥 예제)

class UserStorage {
  loginUser(id, password, onSuccess, onError) {
    setTimeout(() => {
      if (
        (id === 'ellie' && password === 'dream') ||
        (id === 'coder' && password === 'academy')
      ) {
        onSuccess(id); 
        // (로그인 성공 시 onSuccess 콜백 호출)
      } else {
        onError(new Error('not found')); 
        // (로그인 실패 시 onError 콜백 호출)
      }
    }, 2000); // (2초 후 로그인 처리)
  }

  getRoles(user, onSuccess, onError) {
    setTimeout(() => {
      if (user === 'ellie') {
        onSuccess({ name: 'ellie', role: 'admin' }); 
        // (권한 확인 성공 시 onSuccess 콜백 호출)
      } else {
        onError(new Error('no access')); 
        // (권한이 없으면 onError 콜백 호출)
      }
    }, 1000); // (1초 후 권한 확인)
  }
}

const userStorage = new UserStorage();
const id = prompt('enter your id'); 
// (사용자 ID 입력 받음)
const password = prompt('enter your password'); 
// (사용자 비밀번호 입력 받음)

userStorage.loginUser(
  id,
  password,
  user => {
    userStorage.getRoles(
      user,
      userWithRole => {
        alert(
          `Hello ${userWithRole.name}, you have a ${userWithRole.role} role`
        );
        // (성공 시 사용자 역할을 alert로 출력)
      },
      error => {
        console.log(error); 
        // (권한 조회 실패 시 오류 출력)
      }
    );
  },
  error => {
    console.log(error); 
    // (로그인 실패 시 오류 출력)
  }
);

동기와 비동기 (Synchronous vs Asynchronous)

자바스크립트는 기본적으로 동기적(synchronous)이다

개념적으로 JavaScript 는 한 줄씩 순차적으로 실행됩니다.

console.log('1');
setTimeout(() => console.log('2'), 1000);
console.log('3');

 

결과:

1
3
(1초 후)
2

🧾 실행 순서

  1. '1' 출력
  2. setTimeout 등록 → 1초 뒤에 실행하도록 예약
  3. '3' 출력
  4. 1초가 지나면 '2' 출력

📌 왜 이런 순서가 될까?

  • 자바스크립트는 기본적으로 동기적입니다. 즉, 한 줄씩 순서대로 실행합니다.
  • 하지만 setTimeout()과 같은 함수는 비동기 처리로 예약됩니다.
  • 따라서 '1', '3'이 먼저 출력되고, '2'는 1초 뒤에 비동기로 출력됩니다.

콜백 함수 (Callback Function)

콜백 함수는 다른 함수에 인자로 전달되어 나중에 호출되는 함수입니다.

동기 콜백

function printImmediately(print) {
  print();
}
printImmediately(() => console.log('hello'));
  • printImmediately는 전달받은 print 함수를 즉시(synchronous) 실행합니다.
  • 결과: 'hello'가 바로 출력됨.

비동기 콜백

function printWithDelay(print, timeout) {
  setTimeout(print, timeout);
}
printWithDelay(() => console.log('async callback'), 2000);

 

  • printWithDelay는 전달받은 print 함수를 지정된 시간 후 비동기적으로 실행합니다.
  • 결과: 2초 후 'async callback' 출력

콜백 지옥 (Callback Hell)

복잡한 비동기 작업을 콜백 함수 안에 또 콜백 함수를 넣는 방식으로 처리할 경우, 코드가 지저분하고 읽기 어렵게 됩니다.

📌 문제점은?

  • 콜백 안에 콜백이 중첩되면서 코드가 오른쪽으로 점점 밀림 → 😱 가독성 저하
  • 하나라도 실수하거나 빠뜨리면 전체 흐름이 망가짐
  • 유닛 테스트(부분 테스트)가 어려움 → 각각의 단계가 함수 내부 깊숙이 있어서 테스트 불가능
  • 에러 추적이 어려움 → 어느 단계에서 실패했는지 파악 힘듦

아이디, 비밀번호 입력
올바른 로그인 정보일 때 / 잘못된 정보를 입력했을 때

정리

비동기 특정 작업이 완료된 뒤 실행되도록 예약됨 (setTimeout, API 호출 등)
콜백 함수 나중에 실행되도록 함수에 전달된 함수
콜백 지옥 콜백 안에 콜백이 반복되어 코드가 복잡해지는 현상
동기 코드가 위에서 아래로 순서대로 실행됨