예를 들어 코드가 A → B → C 순서로 실행된다고 가정하자. 이때 A가 외부 서버와 통신(API 호출)처럼 오래 걸리는 작업이라면, B와 C는 A가 끝날 때까지 대기해야 한다.
싱글스레드 환경에서 이런 대기 시간을 줄이기 위해 A를 비동기로 처리한다. 즉, “A는 요청만 보내고, 완료되면 알려줘(콜백/프로미스/async-await로 이어서 처리할게). 그동안 B, C는 먼저 진행해”라는 방식이다.
JavaScript에서 함수는 “값”처럼 다룰 수 있기 때문에(변수에 담거나, 다른 함수의 인자로 전달 가능), 특정 시점에 실행할 로직을 함수 형태로 넘기는 패턴이 매우 자주 등장한다. 이때 인자로 전달되는 함수를 콜백(callback) 함수라고 부른다.
아래 예시는 forEach가 배열을 순회하면서 콜백을 즉시 호출하는 형태라, 실행이 순서대로 진행된다.
// 동기 콜백 예시
const nums = [1, 2, 3];
nums.forEach((n) => {
console.log("현재 값:", n);
});
console.log("forEach 종료"); // 항상 마지막에 출력
setTimeout은 “지정된 시간이 지난 뒤” 콜백을 실행하므로, 코드 작성 순서와 실제 실행 순서가 달라질 수 있다.
// 비동기 콜백 예시
console.log("A 시작");
setTimeout(() => {
console.log("B (나중에 실행)");
}, 0);
console.log("C 끝");
// 출력 순서 예: A 시작 → C 끝 → B (나중에 실행)
onClick={handleClick} (O) / onClick={handleClick()} (X: 즉시 실행)
const basicFunction = () => {
console.log("hello world");
}
const callBackFunction1 = () => {
callBackTest(basicFunction);
}
const callBackFunction1 = () => {
callBackTest(() => {
console.log("hello java");
});
}
const callBackFunction2 = () => {
const numbers = [1, 2, 3, 4, 5];
numbers.forEach((number) => {
console.log(number);
});
}
setTimeout(): n초 뒤에 요청한 작업을 처리해주는 함수
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 응답을 요청하고, 해당 응답값을 받아서 실행해야하는 코드가 있다면 문제 발생(비동기내에서 동기적인 상황을 보장해야하는 경우)
콜백지옥 시작