콜백함수는 간단하게 ”매개변수로 함수 객체를 전달해서 호출 함수 내에서 매개변수 함수를 실행하는 것” 을 의미한다.
에를 들어 아래 코드와 같이 seyHello() 함수가 호출될 때 입력 매개변수로 분자열과 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);
});
보통 콜백 함수는 호출 함수에 일회용으로 사용되는 경우가 많다. 따라서 코드의 간결성을 위해 이름이 없는 ’익명의 함수’ 를 사용한다. 그 이유는 함수 내부에서 매개변수를 통해 실행되기 때문에 이름을 붙이지 않아도 되기 때문이다.
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) {...}
자바스크립트의 화살표 함수를 통해 ‘익명 화살표 함수’ 형태로 정의하여 콜백함수를 사용할 수 있다.
function sayHello(callback) {
let name = "도비";
callback(name); // 콜백 함수 호출
}
// 익명 화살표 콜백 함수
sayHello((name) => {
console.log("안녕, " + name);
}); // 안녕, 도비
자바스크립트는 일급 객체의 특성을 가지고 있다. 따라서 자바스크립트는 null 과 undefined 타입을 제외한 모든 것들을 객체로 다룬다. 그렇기 때문에 매개변수에 일반적인 변수나 상수값 뿐만 아니라 함수 자체를 객체로서 저달이 가능한 것이다.
만일 콜백 함수가 일회용이 아닌 여러 호출 함수에 재활용으로 자주 사용될 경우, 별도로 함수를 정의하여 함수의 이름만 호출 함수의 인자에 전달하는 식으로 사용이 가능하다.
// 콜백 함수를 별도의 함수로 정의
function greet(name) {
console.log("안녕, " + name);
}
function sayHello(callback) {
let name = "Dobby";
callback(name); // 콜백 함수 호출
}
function sayHello2(callback) {
let name = "도비";
callback(name); // 콜백 함수 호출
}
// 콜백 함수의 이름만 인자로 전달
sayHello(greet); // 안녕, Dobby
sayHello2(greet); // 안녕, 도비
이러한 특징을 응용하면, 매개변수에 전달할 콜백 함수 종류만을 바꿔줌으로서 여러가지 함수 형태를 다양하게 전달할 수 있다.
아래와 같이 다른 동작을 수행하는 함수 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);
// 결과 -> 지금까지 김도비였습니다. 안녕히계세요

콜백 지옥이란 함수의 매개변수로 넘겨지는 콜백 함수들이 여러번 중첩되면서 코드가 피라미드처럼 깊어지고 복잡해지는 현상을 의미한다.
// 콜백 지옥 예시
step1(function (value1) {
step2(value1, function (value2) {
step3(value2, function (value3) {
step4(value3, function (value4) {
// ... 계속 깊어지는 코드
});
});
});
});
이러한 코드는 세가지 큰 문제점을 갖는다.
ES6에서 도입된 Promise 를 사용하여 콜백을 중첩시키는 대신 .then() 메소드를 체인(chain) 처럼 연결해서 코드를 평평하게 만들 수 있다.
step1()
.then((value1) => step2(value1))
.then((value2) => step3(value2))
.then((value3) => step4(value3))
.catch((error) => {
// 모든 단계의 에러를 한 곳에서 처리!
console.error(error);
});
async/await 사용하기ES2017에서 도입된 async/await 는 Promise를 더 쉽게 사용할 수 있도록 만든 문법적 설탕(Syntactic Sugar)이다. 비동기 코드를 마치 동기 코드처럼 순서대로 읽기 쉽게 만들어준다.
async function executeSteps() {
try {
const value1 = await step1();
const value2 = await step2(value1);
const value3 = await step3(value2);
const value4 = await step4(value3);
console.log(value4);
} catch (error) {
// try-catch 구문으로 에러 처리도 아주 깔끔!
console.error(error);
}
}
executeSteps();