[Javascript] 비동기 처리 - (1) 콜백 함수

2025. 7. 1. 09:00Javascript/Javascript

728x90
반응형

 

 

 

콜백 함수는 흔히 비동기를 다룰 때 자주 엮여 등장하는 개념이다. 콜백 함수는 자바스크립트의 일급 객체 특성을 이용해 함수의 매개변수에 함수 자체를 넘겨 함수 내에서 매개변수 함수를 실행하는 기법을 말한다.

 

자세한 내용은 아래 포스팅 참조

 

[Javascript] 콜백 함수

콜백(Callback) 함수. 간단히 말하면 파라미터로 함수 객체를 전달해서 호출 함수 내에서 파라미터로 전달된 함수를 실행하는 것을 말한다.예를 들어 아래 코드와 같이 sayHello() 함수를 호출할때 파

developing-move.tistory.com

 

 

 

콜백 함수

먼저 사용자 ID를 파라미터로 받아 DB나 API 연동 없이 임의의 사용자 객체를 반환하는 함수를 만들어 보는 것으로 시작하려 한다.

function findUser(id) {
  const user = {
    id: id,
    name: "User" + id,
    email: id + "@test.com",
  };
  return user;
}

const user = findUser(1);
console.log("user:", user); // user: {id: 1, name: "User1", email: "1@test.com"}

 

위와 같이 우리가 흔히 생각하는 일반적인 함수란 입력(파라미터)이 있고 출력(리턴 값)이 있다. 하지만 자바스크립트에서는 출력값이 없고 그 대신 콜백 함수를 입력 받는 함수들이 있다. 콜백 함수는 다른 함수에 인자로 넘겨 실행될 로직을 담게 된다.

 

예를 들면 위 코드는 아래와 같이 다른 방식으로 동일한 결과를 출력하도록 재작성할 수 있다.

function findUserAndCallBack(id, cb) {
  const user = {
    id: id,
    name: "User" + id,
    email: id + "@test.com",
  };
  cb(user);
}

findUserAndCallBack(1, function (user) {
  console.log("user:", user); // user: {id: 1, name: "User1", email: "1@test.com"}
});

 

여기서 findUserAndCallBack() 함수의 호출부를 보면 두 번째 인자로 콜백 함수를 선언하여 넘긴 것을 볼 수 있다. 따라서 findUserAndCallBack() 함수가 실행될 때 매개변수 cb는 콜백 함수를 할당 받으며 cb(user)가 실행될 때 이 콜백함수가 실행되는 것이다.

 

위 두 코드의 차이점은 첫 번째 예시의 findUser()는 결과 값을 리턴하고 함수 외부에서 그 값을 이용해 작업을 수행하는 반면, 두 번째 예시의 findUserAndCallBack()는 결과 값을 이용해서 해야 할 작업까지 함수 내부에서 수행해 주기 때문에 결과 값을 굳이 리턴할 필요가 없다는 것이다.

 

 


 

 

콜백 함수를 이용한 비동기 처리

 

비동기 함수란 쉽게 말해서 호출부에서 실행 결과를 기다리지 않아도 되는 함수이다. 비동기 함수의 이러한 Non-blocking 이점 덕분에 싱글 스레드 환경에서 실행되는 언어에서 광범위하게 사용된다. 예를 들어 브라우저에서 어떠한 로직이 비동기 함수만으로 실행될 경우 기다리는 시간이 길어진다면 사용자 경험에 부정적인 영향을 미치게 될 것이다. 또한 비동기 방식을 사용하면 로직을 순차적으로 처리할 필요가 없기 때문에 동시 처리에서도 동기 방식 대비 유리한 것으로 알려져 있다.

 

하지만 개발자 입장에서 비동기 방식은 동기 방식에 비해 덜 직관적으로 느껴질 수 있다. 동기 방식과 같이 순차적 처리가 보장되지 않기 때문에 아래에 위치한 코드가 위에 있는 코드보다 먼저 실행될 수 있기 때문이다.

 

 

자바스크립트에는 setTimeout()이라는 내장 비동기 함수가 있다. setTimeout()은 두 개의 매개변수를 받는데 첫 번째는 실행할 작업 내용을 담은 콜백 함수이고 두 번째는 이 콜백 함수를 수행하기 전에 기다리는 밀리세컨드 단위의 시간이다. 즉 setTimeOut()은 두 번째 파라미터로 들어온 시간만큼 기다린 후에 첫 번째 파라미터로 들어온 콜백 함수를 실행하는 것이다.

setTimeout(() => console.log("2초 후에 실행됨"), 2000);

 

실제 프로젝트에서 DB나 API를 통해서 유저 데이터를 얻어오는 경우 필연적으로 이러한 지연이 발생하게 된다. 이러한 상황을 시뮬레이션하기 위해 setTimeout을 이용해 위에서 예로 들었던 findUser() 함수를 수정한 결과이다.

 

function findUser(id) {
  let user;
  setTimeout(function () {
    console.log("waited 0.1 sec.");
    user = {
      id: id,
      name: "User" + id,
      email: id + "@test.com",
    };
  }, 100);
  return user;
}

const user = findUser(1);
console.log("user:", user); 

// 실행 결과
// user: undefined
// waited 0.1 sec.

 

위 코드를 실행해 보면 예상치 못한 순서로 코드가 실행되는 것을 알 수 있다. 세 번째 라인의 setTimeout()은 비동기 함수의 호출이기 때문에 실행이 완료될 때까지 기다리지 않고 다음 라인인 return user로 넘어가버린다. 따라서 findUser(1)은 undefined를 리턴하게 되고 user 변수에는 그대로 undefined가 할당되는 것이다.  그리고 0.1초 후 setTimeout()의 콜백함수가 실행되면서 waited 0.1 sec가 출력되고 user에는 원하는 객체가 할당되었지만 이미 때는 늦어버리게 되었다.

 

이와 같이 비동기 함수를 호출하게 되면 함수의 실행이 완료도 되기 전에 다음 라인이 실행되어버리기 때문에 각별한 주의가 필요하다. 그러나 코드 실행 순서가 뒤섞일 수 있는 난처한 상황에서는 콜백 함수를 이용해 해결할 수 있다. 함수로부터 결과 값을 리턴 받기를 포기하고 결과 값을 이용해 처리할 로직을 콜백 함수에 담아 인자로 던지면 되는 것이다.

 

function findUserAndCallBack(id, cb) {
  setTimeout(function () {
    console.log("waited 0.1 sec.");
    const user = {
      id: id,
      name: "User" + id,
      email: id + "@test.com",
    };
    cb(user);
  }, 100);
}

findUserAndCallBack(1, function (user) {
  console.log("user:", user);
});

// 결과
// waited 0.1 sec.
// user: {id: 1, name: "User1", email: "1@test.com"}

 

이번에는 findUserAndCallBack()의 두 번째 인자로 결과 값을 이용해 실행될 로직을 넘겼고 setTimeout()은 0.1초 후에 이 함수를 호출하였다. 이처럼 비동기 함수를 호출할 때에는 결과 값을 통해 처리할 로직을 콜백 함수로 넘겨야 예상된 결과를 얻을 수 있다.

 

하지만 자바스크립트 프로젝트가 점점 더 복잡해지면서 최근에는 콜백 함수를 인자로 넘겨서 비동기 처리를 하는 스타일을 피하는 추세이다. 콜백 함수를 중첨해서 사용하게 되면 계속해서 코드를 들여쓰기해야 하고 그럴수록 코드 가족성이 현저히 떨어지게 되기 때문이다. 결국 많은 개발자들이 콜백 지옥이라 불리는 끔찍한 상황을 겪게 되었고 Promise나 async/await을 이용하는 방법들로 대체되고 있다.

 

 

 

 

 

 

 

728x90
반응형