[Javascript] 콜백 함수

2025. 5. 23. 09:00Javascript/Javascript

728x90
반응형

 
 

콜백(Callback) 함수. 간단히 말하면 파라미터로 함수 객체를 전달해서 호출 함수 내에서 파라미터로 전달된 함수를 실행하는 것을 말한다.

예를 들어 아래 코드와 같이 sayHello() 함수를 호출할때 파라미터와 문자열과 printing 함수 자체를 전달하는 것. 그리고 sayHello()가 실행되면 실행문 안에서 함수가 담긴 두 번째 파라미터 callback 을 괄호()를 붙여 호출한다.

function sayHello(name, callback) {
    const words = '안녕하세요 저는 ' + name + '입니다.';
    
    callback(words); // 파라미터의 함수(콜백 함수) 호출
}

sayHello("아오금", function printing(name) {
	console.log(name); // 안녕하세요 저는 아오금입니다.
});


그러니까 콜백 함수라는 건 파라미터로 일반적인 변수나 값을 전달하는 것 이외에 함수 자체를 전달하는 것을 의미한다고 볼 수 있다.

어차피 파라미터에 함수를 전달해 일회용으로 사용하기 때문에 굳이 함수의 이름을 명시할 필요가 없어 보통 콜백 함수 형태로 함수를 넘겨줄 때에는 함수의 이름이 없는 '익명 함수' 형태로 넘기는 것이 일반적이다.

function sayHello(name, callback) {
    const words = '안녕하세요 저는 ' + name + '입니다.';
    
    callback(words);
}

sayHello("아오금", function (name) { // 함수의 이름이 없는 익명 함수 사용
	console.log(name); 
});





콜백 함수의 사용 원칙



1. 익명 함수 사용

위에서도 언급했듯이 보통 콜백 함수는 호출 함수에서 일회성으로 사용하는 경우가 많아 코드의 간결성을 위해 이름이 없는 '익명 함수'를 사용하는 편이다. 함수의 내부에서 파라미터를 통해 실행되기 때문에 이름을 붙이지 않아도 되기 때문이다.

sayHello("아오금", function (name) { // 함수의 이름이 없는 익명 함수
	console.log(name); 
});


이밖에도 익명 함수를 사용하는 데에는 뜻하지 않은 개발자의 실수로 인한 함수 이름의 충돌 방지 목적도 있다. 콜백 함수에 이름을 붙이면 그 이름은 함수 스코프 내에서 유효한 식별자가 되기 때문에 만약 같은 스코프 내에 이미 같은 이름의 식별자가 있다면 콜백 함수의 이름이 기존의 식별자를 덮어쓰게 되어 버린다. 이는 의도치 않은 결과를 초래할 수 있다.

예를 들어 아래 코드와 같이 변수 add가 이미 있는 상황에 콜백 함수의 이름을 add로 지정할 경우 콜백 함수가 변수의 값을 변경해 버리게 되는 것이다.

let add = 10; // 변수 add

function sum(x, y, callback) {
  callback(x + y); // 콜백함수 호출
}

// 이름 있는 콜백 함수 작성
sum(1, 2, function add(result) {
  console.log(result); // 3
});

// 변수 add가 함수 add가 되어버린다.
console.log(add); // function add(result) {...}




2. 화살표 함수 모양의 콜백

콜백 함수를 익명 함수로 정의함으로써 코드의 간결성을 얻을 수 있지만 한 단계 더 간결함을 얻기 위해서는 화살표 함수를 통해 ‘익명 화살표 함수’ 형태로 정의해 사용할 수 있다. 이러한 익명 화살표 콜백 함수 형태는 자바스크립트 내에서 정말 자주 접하게 되는 것 중 하나이다.

function sayHello(callback) {
  var name = "Alice";
  callback(name); // 콜백 함수 호출
}

// 익명 화살표 콜백 함수
sayHello((name) => {
  console.log("Hello, " + name);
}); // Hello, Alice




3. 함수 이름 넘기기

자바스크립트는 일급 객체의 특성을 가지고 있기 때문에  null과 undefined 타입을 제외한 모든 것을 객체로 다룬다. 그래서 매개변수에 일반적인 변수나 상수값 뿐만 아니라 함수 자체를 객체로 전달이 가능한 것이다. 만약 콜백 함수가 일회용이 아닌 여러 호출 함수에 의해 재활용될 수 있는 경우 별도로 함수를 정의하고 함수의 이름만 호출 함수의 인자에 전달하는 식으로 사용이 가능하다.

// 콜백 함수를 별도의 함수로 정의
function greet(name) {
  console.log("Hello, " + name);
}

function sayHello(callback) {
  var name = "Alice";
  callback(name); // 콜백 함수 호출
}

function sayHello2(callback) {
  var name = "John";
  callback(name); // 콜백 함수 호출
}

// 콜백 함수의 이름만 인자로 전달
sayHello(greet); // Hello, Alice
sayHello2(greet); // Hello, John


이러한 특징을 잘 응용하면 매개변수에 전달할 콜백 함수 종류만을 바꿔줌으로써 여러 가지 함수 형태를 다양하게 전달이 가능하다. 아래와 같이 다른 동작을 수행하는 함수 say_hello 와 say_bye 를 정의해두고 introduce 함수의 입력 값으로 각각 다른 콜백 함수를 전달해주면, introduce라는 함수에서 다른 동작을 수행하는 것이 가능해진다.

function introduce (lastName, firstName, callback) {
    var fullName = lastName + firstName;
    
    callback(fullName);
}

function say_hello (name) {
    console.log("안녕하세요 제 이름은 " + name + "입니다");
}

function say_bye (name) {
    console.log("지금까지 " + name + "이었습니다. 안녕히계세요");
}

introduce("홍", "길동", say_hello);
// 결과 -> 안녕하세요 제 이름은 홍길동입니다

introduce("홍", "길동", say_bye);
// 결과 -> 지금까지 홍길동이었습니다. 안녕히계세요





콜백 함수의 활용 사례


위에서 본 것과 같이 직접 콜백 함수를 만들어 사용하기도 하지만 이미 우리에게는 자바스크립트에서 콜백 함수를 사용하는 여러 가지 메서드를 이용해 본 경험이 있다.
 

1. 이벤트리스너로 사용

addEventListener는 특정 이벤트가 발생했을 때 콜백 함수를 실행하는 메서드이다. 클릭 등의 이벤트를 처리하기 위해 등록하는 이벤트리스너로 콜백 함수가 쓰인다. 버튼을 클릭하면 그에 연관되는 스크립트 실행을 콜백 함수로 등록하는 방식인 것이다.

let button = document.getElementById("button"); // 버튼 요소를 선택

// 버튼에 클릭 이벤트 리스너를 추가
button.addEventListener("click", function () { // 콜백 함수
  console.log("Button clicked!"); 
});



2. 내장함수에서의 사용

자바스크립트에서 일반적인 for문 보다 더 자주 사용되는 반복문이 forEach 메서드일 거라고 생각한다. 이 또한 forEach 메서드의 입력 값으로 콜백 함수를 전달하는 형태임을 볼 수 있다.

// 예시 : 배열의 각 요소를 두 배로 곱해서 새로운 배열을 생성하는 콜백 함수 
let numbers = [1, 2, 3, 4, 5]; // 배열 선언 
let doubled = []; // 빈 배열 선언 

// numbers 배열의 각 요소에 대해 콜백 함수 실행 
numbers.forEach(function (num) { 
    doubled.push(num * 2); // 콜백 함수로 각 요소를 두 배로 곱해서 doubled 배열에 추가 
}); 

console.log(doubled); // [2, 4, 6, 8, 10]



3. Ajax의 결과값을 받을 때
서버와 데이터를 주고받을 때 사용하는 fetch 메서드 등에서 서버 요청에 대한 결과 값을 처리하기 위해 콜백 함수가 사용된다.

// fetch 메서드를 사용하여 서버로부터 JSON 데이터를 받아오고 콜백 함수로 화면에 출력
fetch("https://jsonplaceholder.typicode.com/users")
  .then(function (response) {
    // fetch 메서드가 성공하면 콜백 함수로 response 인자를 받음
    return response.json(); // response 객체의 json 메서드를 호출하여 JSON 데이터를 반환
  })
  .then(function (data) {
    // json 메서드가 성공하면 콜백 함수로 data 인자를 받음
	console.log(data);
  })



4. 타이머의 실행 함수로 사용
setTimeout이나 setInterval과 같은 타이머 함수에서 일정 시간마다 스크립트를 실행하는 용도로 콜백 함수를 이용한다.

// 3000 밀리초(3초) 후에 콜백 함수 실행
setTimeout(function () {
  console.log("Time is up!"); // 콜백 함수로 콘솔에 메시지 출력
}, 3000);




콜백함수 사용 시 주의사항


위에서 본 것과 같이 단순히 함수의 파라미터에 함수를 넘겨주면 끝이라 생각하기 쉽지만 자바스크립트의 콜백 함수 이용에는 몇 가지 주의점이 있다.


this를 사용한 콜백 함수


콜백 함수 내에서 this 키워드를 사용하면 예상한 대로 작동하지 않을 수 있다. 우선 아래 코드를 보자.

let userData = {
    signUp: '2021-4-06 12:00:00',
    name: 'Not Set',
    setName: function(firstName, lastName) {
        this.name = firstName + ' ' + lastName;
    }
}

function getUserName(firstName, lastName, callback) {
    callback(firstName, lastName);
}

getUserName('홍', '길동', userData.setName);

console.log('1: ', userData.name); // Not Set
console.log('2: ', window.name); // 홍 길동


getUserName 함수를 실행한 후 첫 번째 콘솔의 값이 '홍길동'이기를 예상했지만, Not Set이 출력되는 걸 볼 수 있다. userData 객체의 setName 프로퍼티 함수 내부에서 사용된 this.name이 userData 객체의 name을 가리키는게 아니라 전역 객체 window의 name을 가리키기 때문이다.


콜백 함수의 this가 전역 객체인 이유

콜백 함수는 다른 함수의 인자로 전달되는 함수다. 그래서 콜백 함수는 자신을 전달받은 함수에 의해 호출되는데  이때 콜백 함수 내부에서의 this는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 전역 객체를 참조하게 된다.

아래의 코드에서는 userData.setName('홍', '길동')과 같이 직접 객체의 메서드 호출 방식으로 실행되었다. 메서드 호출 방식에서 this는 함수가 들어 있는 자기 자신 객체인 userData를 가리킨다. 따라서 정상적으로 userData.name 프로퍼티 값이 업데이트 된다.

let userData = {
    signUp: '2021-4-06 12:00:00',
    name: 'Not Set',
    setName: function(firstName, lastName) {
        this.name = firstName + ' ' + lastName;
    }
}

userData.setName('홍', '길동');

console.log(userData.name); // 홍 길동


반면 아래 예시에서는 getUserName('홍', '길동', userData.setName)과 같이 userData.setName가 콜백 함수 방식으로 전달되고 있다.  여기서는userData.setName이라는 코드에서도 볼 수 있듯이  setName 함수가 userData 객체 안에 있다는 것을 나타내기 때문에 일반적으로 setName 함수 내의 this 키워드는 당연히 userData 객체를 가리키는 것으로 이해할 수 있다.

function getUserName(firstName, lastName, callback) {
    callback(firstName, lastName);
}

getUserName('홍', '길동', userData.setName);

console.log('1: ', userData.name); // Not Set
console.log('2: ', window.name); // 홍 길동


그러나 콜백 함수로 전달될 때에는 상황이 다르다. 생긴 건 객체 참조의 메서드 모양으로 들어가 있지만 사실은 객체의 메서드를 그대로 넘긴 게 아니라 그냥 콜백 함수의 역할을 하는 함수로써 넘긴 것 뿐이다. 즉 이 상황을 풀어보면 아래와 같이 되는 것이다.

let userData = {
    signUp: '2021-4-06 12:00:00',
    name: 'Not Set',
    setName: function(firstName, lastName) {
        this.name = firstName + ' ' + lastName;
    }
}

function getUserName(firstName, lastName, callback) {
    callback(firstName, lastName);
}

// 해당 콜백 함수는 userData.setName과 아무런 관계가 없는 함수 형태만 비슷한 독립적인 함수이다.
getUserName('홍', '길동', function(firstName, lastName) {
	this.name = firstName + ' ' + lastName;
});

console.log('1: ', userData.name); // Not Set
console.log('2: ', window.name); // 홍 길동


userData 객체와 아무런 연관성이 없는 독립적인 메서드이기 때문에 콜백 함수 내의 this 는 userData 객체를 가리키지 않는다. 그리고 일반적으로 콜백 함수를 호출하는 함수들은 별도로 this를 설정하지 않으므로 기본적으로 전역 객체가 바인딩 되는 것이다.


콜백 함수 this의 해결 방안


따라서 콜백 함수 내의 this를 보호할 수 있도록 콜백 함수를 만들어야 한다.

1. call, bind, apply 메서드 사용
첫 번째 해결 방안으로는 call()이나 apply()를 사용하여 this를 보호하는 것이다.

  • call(): 첫 번째 인자로 this 객체 사용, 나머지 인자들은 , 로 구분
  • apply(): 첫 번째 인자로 this 객체 사용, 나머지 인자들은 배열 형태로 전달


솔루션의 원리는 간단하다. 참조할 객체를 추가로 함수의 파라미터로 전달하고 콜백 함수 내에서 call(), apply() 메서드를 통해 콜백 함수가 참조할 객체를 지정해주면 된다.

// this 대신 userData를 사용하는 방법
let userData = {
    signUp: '2021-4-06 12:00:00',
    name: 'Not Set',
    setName: function(firstName, lastName) {
        this.name = firstName + ' ' + lastName;
    }
}

function getUserName(firstName, lastName, callback, data) { // userData를 받는 매개변수 data를 추가
    callback.call(data, firstName, lastName); // data를 this로 사용
}

getUserName('홍', '길동', userData.setName, userData); // userData를 인수로 전달

console.log('1: ', userData.name); // 홍 길동
console.log('2: ', window.name); // Not Set
// this 대신 userData를 사용하는 방법
let userData = {
    signUp: '2021-4-06 12:00:00',
    name: 'Not Set',
    setName: function(firstName, lastName) {
        this.name = firstName + ' ' + lastName;
    }
}

function getUserName(firstName, lastName, callback, data) { // userData를 받는 매개변수 data를 추가
    callback.apply(data, [firstName, lastName]); // data를 this로 사용하고 배열을 전달
}

getUserName('홍', '길동', userData.setName, userData); // userData를 인수로 전달

console.log('1: ', userData.name); // 홍 길동
console.log('2: ', window.name); // Not Set



2. 화살표 함수 사용
 
또 다른 심플한 방법으로는 화살표 함수를 사용해 this를 외부 함수의 this와 동일하게 유지하는 방법이 있다. 화살표 함수는 자신만의 this를 가지지 않고 상위 스코프의 this를 참조하기 때문에 전역 객체를 무시하고 무조건 자신을 들고 있는 상위 객체를 가리킨다.

let userData = {
    signUp: '2021-4-06 12:00:00',
    name: 'Not Set',
    setName: (firstName, lastName) => { // 화살표 함수로 변경
        this.name = firstName + ' ' + lastName;
    }
}

function getUserName(firstName, lastName, callback) {
    callback(firstName, lastName); // call 메서드 없이 호출
}

getUserName('홍', '길동', userData.setName);

console.log('1: ', userData.name); // 홍 길동
console.log('2: ', window.name); // Not Set




 

콜백 지옥


콜백 지옥이란 함수의 매개변수로 넘겨지는 콜백 함수가 반복되어 코드의 들여쓰기 수준이 감당하기 힘들어질 정도로 깊어지는 현상을 말한다.

function add(x, callback) {
    let sum = x + x;
    console.log(sum);
    callback(sum);
}

add(2, function(result) {
    add(result, function(result) {
        add(result, function(result) {
            console.log('finish!!');
        })
    })
})

/*
4
8
16
finish!!
*/


위 예시는 아주 기본적인 부분만 보여줘서 위 코드만 보면 조금 껄끄럽긴 해도 지옥 정도는 아니라고 생각할 수도 있지만 콜백 지옥으로 유명한 아래 그림을 보면 말이 달라질 것이다.




비동기와 콜백


자바스크립트는 싱글 스레드 언어기 때문에 한 번에 하나의 작업만 처리할 수 있다. 또한 코드를 위에서부터 아래로 순차적으로 실행한다. 그러나 웹 개발에서는 네트워크 요청이나 타이머 등의 작업이 필요한 경우가 많은데 이러한 작업들은 시간이 오래 걸리거나 결과가 불확실하다. 그래서 자바스크립트는 비동기(asynchronous) 방식으로 작업을 처리하는 기법을 제공한다.


비동기란 현재 실행 중인 작업을 멈추지 않고 다른 작업을 병렬적으로 수행하는 것을 의미한다. 따라서 여러 작업들이 있을 때 비동기적으로 수행하면 작업들을 동시에 한 번에 수행할 수 있어 위 사진과 같이 결과적으로 최종 작업 수행이 빠르게 처리된다. 그리고 이러한 비동기 방식으로 작업을 처리하는 방법 중 하나가 바로 콜백(callback) 함수이다.

 




728x90
반응형