[Javascript] 콜백개념, 동기/비동기

이지연·2026년 1월 8일

개요

멀티/싱글 스레드 → 콜백 개념 → 동기/비동기 예시 → 일반함수/콜백함수 실습 → forEach → setTimeout → 콜백지옥 순서로 실습을 진행하였다.


멀티스레드 vs 싱글스레드

Java와 JavaScript를 비교할 때 가장 먼저 잡아야 하는 차이는 “실행 모델”이다.

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

Java(멀티스레드)의 장단점

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

JavaScript(싱글스레드)의 장단점

  • 장점: 한 번에 한 흐름으로 실행되므로 동시성 문제를 직접 다룰 일이 상대적으로 적어 구조가 단순하다.
  • 단점: 한 작업이 오래 걸리면 다음 작업이 밀려서 성능/응답성이 떨어질 수 있다.

그래서 JavaScript는 “싱글스레드”라는 기반 위에서, 오래 걸리는 작업 때문에 전체가 멈추는 상황을 줄이려고 비동기(Asynchronous) 패턴을 적극적으로 사용한다.


비동기 처리가 필요한 이유(병목 예시)

코드가 A → B → C 순서로 실행된다고 가정하자.

  • A가 외부 서버 통신(API 요청)처럼 오래 걸리는 작업이면
  • B와 C는 A가 끝날 때까지 기다려야 해서 “병목”이 생긴다.

이 문제를 줄이는 전형적인 방법이 A를 비동기적으로 시작해 두고, A가 끝나기 전이라도 B, C를 먼저 진행하는 방식이다.
이때 “A가 끝났을 때 해야 하는 후속 처리”를 등록해두는 대표적인 도구가 콜백 함수다.


일반함수 & 콜백 함수

JavaScript에서 함수는 “코드 블록”이면서 동시에 “값(데이터)”처럼 다룰 수 있다.
그래서 함수를 변수에 담을 수도 있고, 다른 함수의 인자로 넘길 수도 있는데, 다른 함수에 인자로 전달되는 함수를 콜백(callback) 함수라고 부른다.

콜백 함수가 자주 등장하는 곳

  • 배열 메서드: forEach, map, filter
  • 이벤트 처리: click, scroll
  • 타이머: setTimeout
  • 네트워크 요청 후 처리(API 응답 처리)

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

콜백 함수 자체가 “동기/비동기”를 결정하는 게 아니라, 그 콜백을 호출하는 주체가 언제 호출하느냐가 핵심이다.

  • 동기: 코드가 위에서 아래로 순서대로 실행되며, 이전 작업이 끝나야 다음 줄로 넘어간다.
  • 비동기: 어떤 작업은 완료를 기다리지 않고 다음 줄로 넘어가고, 완료 시점에 등록해 둔 콜백(또는 후속 로직)이 실행된다.

실습 1) 일반함수 호출

가장 기본적인 함수 호출은 “정의한 함수를 직접 실행”하는 것이다.

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

버튼을 눌렀을 때 콘솔에 "hello world"가 찍히면, “함수 정의 → 호출” 흐름이 정상적으로 잡힌 것이다.


실습 2) 콜백함수 기본 형태 2가지

콜백을 쓰는 목적은 “지금이 아니라, 특정 시점에 실행할 로직을 전달”하기 위해서다.
네 예제는 콜백 형태를 가장 대표적인 2가지로 보여준다.

case1) 이미 정의된 함수를 인자로 전달

callBackTest(basicFunction);

case2) 함수를 리터럴로 바로 작성해서 전달

callBackTest(() => {
  console.log("hello java");
});

그리고 callBackTest는 전달받은 함수를 내부에서 실행한다.

const callBackTest = (f1) => {
  f1();
}

여기서 핵심은 함수 자체를 넘긴다는 점이다.
basicFunction을 넘기는 것이고, basicFunction()처럼 “호출 결과”를 넘기는 게 아니다.


실습 3) 함수형 프로그래밍에서 콜백(forEach)

배열 메서드는 콜백을 굉장히 자연스럽게 사용한다.
forEach는 배열의 각 요소에 대해 콜백을 실행한다.

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

이 패턴이 익숙해지면, 반복문을 “의도 중심 코드”로 바꾸는 연습(가독성)이 된다.


실습 4) 콜백 + 비동기(setTimeout)

setTimeout은 대표적인 “비동기 + 콜백” 함수다.
네 예제는 아래 구조로 되어 있다.

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

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

여기서 포인트는 이거다.

  • setTimeout을 호출하는 순간, “2초 뒤 실행할 콜백”이 등록된다.
  • 등록 직후에는 기다리지 않고 바로 다음 줄로 내려가서 python1, python2가 먼저 찍힐 수 있다.
  • 이후 시간이 지나면 등록해둔 콜백이 실행되며 java1, java2가 찍힌다.

즉, 코드 작성 순서와 출력 순서가 달라질 수 있다는 점이 “비동기” 체감 포인트다.


콜백지옥(Callback Hell)이 왜 생기나?

비동기 작업을 단순히 “동시에 실행”하는 건 문제가 아닌데, 실무에서는 종종 이런 요구가 생긴다.

  • “A가 끝나야 B를 실행할 수 있고”
  • “B가 끝나야 C를 실행할 수 있는”
    순서 의존 시나리오

이때 콜백만으로 순서를 강제하려고 하면 콜백 내부에 또 콜백이 들어가고, 중첩이 계속 깊어지면서 코드가 읽기 어려워진다.
이 현상을 흔히 “콜백지옥”이라고 부른다.

그래서 이런 흐름은 읽고 다음 게시글에서 Promise, async/await로 개선하는 방향으로 학습 예정!

profile
Eazy하게

0개의 댓글