javascript

자바스크립트 비동기 프로그래밍 - 콜백함수(callback function)

콜백함수(Callback Function)?


콜백함수를 간단하게 정의하면 어떤 특정 함수가 실행을 마친 후에 실행되는 함수를 콜백함수라고 합니다. 조금 더 세부적으로 설명드리면, 자바스크립트에서 함수는 객체입니다. 이러한 특징덕분에 함수는 인자로써 함수를 받을 수 있고, 다른 함수를 리턴할 수 있습니다. 이러한 특징을 가지는 함수를 고차함수(higher-order functions)라고 부릅니다. 인자로 전달된 함수를 콜백함수(callback function)이라고 부르는 거구요.

고차함수(higher-order functions) 조건

  • 함수를 파라미터로 전달받는 함수
  • 함수를 리턴하는 함수

콜백함수가 필요한 이유


콜백 함수를 사용하는 가장 큰 이유 중 하나는 비동기 데이터를 처리하기 위함입니다. 아래의 예시는 우리가 의도한대로 정상적으로 1,2 순서로 콘솔을 찍어 내려감을 확인할 수 있습니다.

function first(){
  console.log(1);
}
function second(){
  console.log(2);
}
first();
second();

// 1
// 2

하지만, 바로 실행될 수 없는 조건이 들어간 함수가 있다면 어떻게 될까요? 예를 들어, setTimeout이나 api 요청을 보내고 응답을 받아오는 타이밍이 존재하는 함수처럼 말입니다. 아래 예시는 setTimeout을 500 밀리초를 설정해서 500밀리초 이후에 동작하는 함수를 구현한 경우입니다.

function first(){
  setTimeout(function(){
    console.log(1);
  }, 500 );
}
function second(){
  console.log(2);
}
first();
second();

// 2
// 1

함수 실행은 first, second 순으로 실행되었지만, 결과는 second, first 순으로 결과 값을 받았음을 확인할 수 있습니다. 여기서 중요한 점은 자바스크립트는 이러한 비동기 처리를 기다려주지 않는다는 점입니다.

이러한 문제를 해결하는 방법 중의 하나가 콜백함수를 사용하는 것입니다. 순서를 보장하기 위해 함수의 인자로 콜백함수를 넣고, 비동기 처리가 끝나는 시점에 콜백함수를 실행시켜주는 것입니다. 쉽게 말해서 콜백함수는 다른 함수의 실행이 끝날 때까지 특정 코드가 실행되지 않게 기다려주는 방법이라고 보면 됩니다.

간단한 callback 예시

function first(callback) {
    setTimeout(function() {
        console.log(1);
		    callback();
    }, 500);
}

function second() {
	console.log(2);
}

first(function() {
  second();
});

// 1
// 2

예시에는 first함수가 실행되고 500밀리초가 지난 후에 1을 콘솔에 찍고 파라미터로 넘겨받은 콜백함수를 실행하면서 second함수가 실행되는 모습을 볼 수 있습니다.

real world 예시

실제 업무에서는 비동기 제어를 하기 위해서 콜백함수를 어떻게 사용할까요? 비동기 요청을 하는 방법은 여러가지 있습니다. jQuery에서는 $.ajax를 활용하는 편이고, 저는 업무에서 vue 라이브러리를 활용할 때 axios 라이브러리를 활용해서 비동기 처리를 하고 있습니다. 물론 axios는 콜백함수를 사용하기 보다는 promise 방식을 통해 비동기 제어를 한다는데에 차이가 조금 있긴합니다.(promise 방식은 다음 포스팅에서 다룰 예정입니다)

jQuery ajax 메소드 활용

아래 예시는 ajax에서 요청 후 응답이 올 때까지 기다렸다가 응답이 성공하면 success 콜백함수로 응답 데이터를 받아오는 구문입니다.

$.ajax({
	url: 'https://httpbin.org/get',
    success: function(res) {
        console.log(res);
    }
});

프론트엔드 환경에서 이렇게 한번의 요청으로 데이터를 받아오고 끝나면 참 평화로울텐데 현실은 그렇지 않습니다. 서버에 요청 후 응답에 성공하면, success 메소드의 콜백함수가 실행되는데 여기서 받은 응답 결과에서 데이터를 받아 추가적으로 호출하는 경우가 많습니다. 이렇게 콜백함수의 깊이가 점점 깊어질수록 코드는 난잡해지고 유지보수하기 코드가 탄생하게 되는데 이런 상황을 보통 콜백지옥이라고 부릅니다.. (callback hell!)

Callback hell

$.ajax({
	url: 'https://httpbin.org/get',
    success: function(res) {
        $.ajax({
        	url: 'https://httpbin.org/get',
          success: function(res) {
            $.ajax({
            	url: 'https://httpbin.org/get',
              success: function(res) {
                $.ajax({
                  url: 'https://httpbin.org/get',
                  success: function(res) {
                    $.ajax({
                      url: 'https://httpbin.org/get',
                      success: function(res) {
                        $.ajax({
                          url: 'https://httpbin.org/get',
                          success: function(res) {
                            $.ajax({
                              url: 'https://httpbin.org/get',
                              success: function(res) {
                                console.log('성공');
                              }
                            })                            
                          }
                        })                        
                      }
                    })                    
                  }
                })                
              }
            })
          }
        })
    }
});

위에 코드가 잘 읽히시나요 극단적으로 요청했지만, 이정도 수준은 아니더라도 꼬리에 꼬리를 물고 비동기 호출을 하는 경우가 빈번합니다. 비동기 호출 구문을 추상화해서 코드를 조금 더 깔끔하게 구성할 수 있지만 그 경우에도 한계가 있습니다. 이러한 콜백지옥을 해결하기 위해서 몇 가지 방법이 있는데 Promise와 Async/Await을 활용하는 것입니다. 다음 포스팅에서는 Promise를 활용해서 비동기 프로그래밍을 조금 더 세련되게 구성하는 방법을 알려드리겠습니다.