자바스크립트 콜백함수
설명
- 콜백(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 = "Namji";
callback(name); // 콜백 함수 호출
}
// 익명 화살표 콜백 함수
sayHello((name) => {
console.log("Hello, " + name);
}); // Hello, Namji
3. 함수 이름 넘기기
- 자바스크립트는 일급 객체의 특성을 가지고 있기 때문에, 자바스크립트는 null과 undefined 타입을제외하고 모든 것을 객체로 다룹니다. 그래서 매개변수에 일반적인 변수나 상수값 뿐만 아니라 함수 자체를 객체로서 전달이 가능한 것입니다. 만일 콜백 함수가 일회용이 아닌 여러 호출 함수에 재활용으로 자주 이용될 경우, 별도로 함수를 정의하고 함수의 이름만 호출 함수의 인자에 전달하는 식으로 사용이 가능합니다.
// 콜백 함수를 별도의 함수로 정의
function greet(name) {
console.log("Hello, " + name);
}
function sayHello(callback) {
var name = "Namji";
callback(name); // 콜백 함수 호출
}
function sayHello2(callback) {
var name = "Nam지";
callback(name); // 콜백 함수 호출
}
// 콜백 함수의 이름만 인자로 전달
sayHello(greet); // Hello, Namji
sayHello2(greet); // Hello, Nam지
- 이러한 특징을 응용하면, 매개변수에 전달할 콜백 함수 종류만을 바꿔줌으로서 여러가지 함수 형태를 다양하게 전달이 가능합니다. 아래와 같이 다른 동작을 수행하는 함수 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 click");
});
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);
5. 애니메이션 완료
- jQeury에서 제공하는 애니메이션 메소드들은 애니메이션이 끝난 후에 실행할 콜백함수를 인자로 받습니다.
// jQuery의 slideUp 메서드를 사용하여 #box 요소를 숨기고 콜백 함수로 콘솔에 메시지 출력
$("#box").slideUp(1000, function () {
console.log("Animation complete"); // 콜백 함수로 콘솔에 메시지 출력
});
자바스크립트 콜백 함수 주의점
- 이처럼 단순히 함수의 파라미터에 함수를 넘겨주면 끝이라 생각하기 쉽지만 자바스크립트의 콜백함수 이용에는 몇가지 조심해야 할 주의점이 있습니다.
1. this를 사용한 콜백함수
- 콜백함수 내에서 this 키워드를 사용하면, 기대한 대로 작동하지 않을 수 있습니다.
let userData = {
signUp: '2023-10-01 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을 가리키기 때문입니다.
2. 콜백 함수 this가 전역 객체인 이유
- 콜백함수는 다른 함수의 인자로 전달되는 함수입니다. 그래서 콜백함수는 자신을 전달받은 함수에 의해 호출되는데, 이때 콜백함수 내부에서의 this는 해당 콜백함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 전역객체를 참조하게 됩니다.
아래의 코드에서 userData.setName('남', '지')와 같이 직접 객체의 메소드 호출 방식으로 실행되었습니다. 메소드 호출 방식에서는 this는 함수가 들어있는 자기 자신 객체인 userData를 가리킨다. 따라서 정상적으로 userdata.name 프로퍼티 값이 업데이트 되게 됩니다.
let userData = {
signUp: '2023-10-01 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: '2023-10-01 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를 설정하지 않으므로 기본적으로 전역 객체가 바인딩 되는 것입니다.
3. 콜백함수 this 해결 방안 (콜백함수 내의 this를 보호하는 방법)
- call, bind, apply 메소드 사용하여 보호
- call() : 첫 번째 인자로 this 객체 사용 나머지 인자들은 ,로 구분
- apply() : 첫 번째 인자로 this 객체 사용 나머지 인자들은 배열 형태로 전달
- 솔루션 원리는 간단합니다. 참조할 객체를 추가로 함수의 매개변수로 전달하고, 콜백함수 내에서 call(), apply() 메소드를 통해 콜백함수가 참조할 객체를 지정해주면 됩니다.
// this 대신 userData를 사용하는 방법
let userData = {
signUp: '2023-10-01 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: '2023-10-01 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
- 화살표 함수 사용
- 또 다른 심플한 방법으로 화살표 함수를 사용하여 this를 외부 함수의 this와 동일하게 유지하는 방법이 잇습니다. 화살표 함수는 자신만의 this를 가지지 않고 상위 스코프의 this를 참조하기 때문에 전역 객체를 무시하고 무조건 자신을 들고 있는 상위 객체를 가리킵니다.
let userData = {
signUp: '2023-10-01 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
콜백 지옥 (Callback Hell)
- 콜백 지옥이란 함수의 매개변수로 넘겨지는 콜백함수가 반복되어 코드의 들여쓰기 수준이 감당하기 힘들어질 정도로 깊어지는 현상입니다.
step1(function (value1) {
step2(function (value2) {
step3(function (value3) {
step4(function (value4) {
step5(function (value5) {
step6(function (value6) {
// Do something with value6
});
});
});
});
});
});
자바스크립트 비동기와 콜백
- 자바스크립트는 싱글 스레드 언어로서, 하나의 작업만을 동시에 처리할 수 있습니다.
즉, 자바스크립트는 코드를 위에서 아래로 순차적으로 실행합니다.
그런데 웹 개발에서는 네트워크 요청이나 타이머 등의 작업이 필요한 경우가 많은데, 이러한 작업들은 시간이 오래 걸리거나 결과가 불확실합니다. 그래서 자바스크립트는 비동기(asynchronous) 방식으로 작업을 처리하는 기법을 제공합니다.
- 비동기란 현재 실행중인 작업을 멈추지 않고 다른 작업을 병렬적으로 수행하는 것을 의미합니다. 그래서 여러작업(task)이 있을 때 비동기적으로 수행하면 작업들을 동시에 수행할 수 있어 위 사진처럼 결과적으로 최종 작업 수행이 빠르게 처리되게 됩니다. 그리고 이러한 비동기 방식으로 작업을 처리하는 방법 중 하나가 바로 콜백(callback) 함수입니다.
콜백함수는 비동기 이벤트 작업을 위해 자바스크립트에서 광범위하게 사용됩니다.
'JavaScript' 카테고리의 다른 글
[JavaScript] - 비교문 / 분기문 / 반복문 / 문자열 (0) | 2023.10.25 |
---|---|
[JavaScript] - 자바스크립트 기본 문법 정리 (0) | 2023.10.25 |
[JavaScript] - Alert 박스 만들기 (0) | 2023.10.21 |
[JavaScript] - 자바스크립트 작성방법 (0) | 2023.10.20 |
[JavaScript] - 변수 / 자료형 / 대화상자 / 형변환 (1) | 2023.10.17 |