콜백함수

개요: 멀티스레드 vs 싱글스레드

실행 모델

  • 멀티스레드: A, B, C, D 작업을 동시에(병렬로) 처리할 수 있음
  • 싱글스레드: A → B → C → D 순서대로 하나씩 처리함

Java (멀티스레드)

  • 장점: 여러 작업을 동시에 처리 가능해서 성능(처리량/응답성)이 좋아질 수 있음
  • 단점: 동시성(공유 자원, 락, 데드락 등)을 고려해야 해서 코드 복잡도가 올라감

JavaScript (싱글스레드)

  • 장점: 한 번에 한 작업 흐름으로 실행되므로 동시성 이슈를 직접 다룰 일이 상대적으로 적어 구조가 단순함
  • 단점: 한 작업이 오래 걸리면 다음 작업이 실행되지 못해 성능/응답성이 떨어질 수 있음
  • 보완: 위 단점을 줄이기 위해 비동기 코드(순서대로 “완료를 기다리지 않고” 다음 작업으로 넘어가는 방식)를 적극 도입함

비동기 처리 예시(병목 해결 흐름)

예를 들어 코드가 A → B → C 순서로 실행된다고 가정하자. 이때 A가 외부 서버와 통신(API 호출)처럼 오래 걸리는 작업이라면, B와 C는 A가 끝날 때까지 대기해야 한다.

싱글스레드 환경에서 이런 대기 시간을 줄이기 위해 A를 비동기로 처리한다. 즉, “A는 요청만 보내고, 완료되면 알려줘(콜백/프로미스/async-await로 이어서 처리할게). 그동안 B, C는 먼저 진행해”라는 방식이다.

  • A(느린 작업: 서버 통신 등)를 비동기 요청으로 시작
  • B, C는 A의 완료를 기다리지 않고 먼저 실행
  • A가 완료되면 완료 시점에 등록해 둔 후속 로직이 실행(결과 반영)
  • 대표적인 사용처: API 호출, 파일/네트워크 I/O 같은 작업

일반함수 & 콜백 함수

JavaScript에서 함수는 “값”처럼 다룰 수 있기 때문에(변수에 담거나, 다른 함수의 인자로 전달 가능), 특정 시점에 실행할 로직을 함수 형태로 넘기는 패턴이 매우 자주 등장한다. 이때 인자로 전달되는 함수를 콜백(callback) 함수라고 부른다.

콜백 함수란?

  • 콜백 함수는 다른 함수의 인자(매개변수)로 전달되어, 그 함수 내부에서 호출되는 함수다.
  • “지금 당장 실행”이 아니라 “특정 조건/시점에 실행”시키고 싶을 때 많이 사용한다.
  • 대표 사용처: 배열 반복(forEach/map/filter), 이벤트 처리(click/scroll), 타이머(setTimeout), 네트워크 요청(fetch) 등의 후속 처리.

동기(Synchronous) vs 비동기(Asynchronous)

  • 동기: 코드가 위에서 아래로 순서대로 실행되고, 이전 작업이 끝나야 다음 줄이 실행된다.
  • 비동기: 어떤 작업은 “완료를 기다리지 않고” 다음 줄로 넘어가며, 완료 시점에 콜백(또는 후속 로직)이 실행된다.
  • JavaScript는 기본적으로 싱글스레드로 동작하지만, 비동기 패턴을 통해 “오래 걸리는 작업 때문에 화면/로직이 멈추는 것”을 줄이는 방향으로 발전해왔다.

예시 1) 동기 콜백(즉시 실행되는 콜백)

아래 예시는 forEach가 배열을 순회하면서 콜백을 즉시 호출하는 형태라, 실행이 순서대로 진행된다.


// 동기 콜백 예시
const nums = [1, 2, 3];

nums.forEach((n) => {
    console.log("현재 값:", n);
});

console.log("forEach 종료"); // 항상 마지막에 출력

예시 2) 비동기 콜백(나중에 실행되는 콜백)

setTimeout은 “지정된 시간이 지난 뒤” 콜백을 실행하므로, 코드 작성 순서와 실제 실행 순서가 달라질 수 있다.


// 비동기 콜백 예시
console.log("A 시작");

setTimeout(() => {
    console.log("B (나중에 실행)");
}, 0);

console.log("C 끝");
// 출력 순서 예: A 시작 → C 끝 → B (나중에 실행)

JS가 싱글스레드인데 비동기가 가능한 이유(핵심 개념)

  • “자바스크립트 코드 실행” 자체는 한 번에 한 줄(한 작업)씩 처리하지만, 타이머/네트워크 요청 같은 작업은 브라우저(Web APIs) 또는 런타임 환경이 처리하고, 완료되면 콜백을 “나중에 실행할 목록”에 올려서 이벤트 루프(event loop)가 적절한 타이밍에 실행한다.
  • 그래서 개발자는 콜백(또는 Promise/async-await)을 이용해 “완료 후 처리”를 등록하는 방식으로 비동기 흐름을 만든다.

콜백을 쓸 때 자주 하는 실수(주의)

  • 콜백을 “호출”해서 넘기지 말고, “함수 자체”를 넘겨야 한다.
    • onClick={handleClick} (O) / onClick={handleClick()} (X: 즉시 실행)
  • 비동기 콜백은 실행 시점이 나중이므로, “값이 이미 바뀐 뒤”를 참조하는 버그가 생길 수 있다(스코프/클로저 주의).
  • 콜백이 여러 단계로 중첩되면 가독성이 급격히 떨어질 수 있어, Promise/async-await로 구조를 개선하는 경우가 많다.

일반함수

const basicFunction = () => {
    console.log("hello world");
}

콜백함수

콜백함수 작성 형태


함수형 프로그래밍에서 콜백함수 활용

const callBackFunction2 = () => {
    const numbers = [1, 2, 3, 4, 5];
    numbers.forEach((number) => {
        console.log(number);
    });
}

콜백함수 + 비동기 활용 예시

const callBackFunction3 = () => {
    setTimeout(() => console.log("hello java1"), 2000);
    console.log("hello python1");

    setTimeout(() => console.log("hello java2"), 2000);
    console.log("hello python2");
}

이 때 실행순서를 보장불가함에 대한 문제 발생(e.g.java1,2 출력코드)

api 응답을 요청하고, 해당 응답값을 받아서 실행해야하는 코드가 있다면 문제 발생(비동기내에서 동기적인 상황을 보장해야하는 경우)

콜백지옥 시작