콜백함수
콜백함수란?
콜백함수란, 다른 함수의 인자로 전달되어 그 함수 내부에서 실행되는 함수를 의미합니다.
즉, 함수를 인자로 전달하고, 이후 특정 시점에 그 함수를 호출하는 방식입니다. 자바스크립트에서는 함수가 일급 객체(First-class citizen) 이기 때문에, 함수도 값처럼 전달할 수 있으며, 이를 활용한 대표적인 기법이 콜백 함수입니다.
콜백함수 제어권
콜백 함수는 단순히 인자로 넘기는 함수가 아니라, 제어권이 중요합니다. 즉 언제, 어떤 인자와 함께, 어떤 this로 호출되는지에 대한 제어권이 콜백을 받는 함수에게 있습니다.
1. 호출 시점의 제어권
콜백 함수는 직접 호출하지 않고, 콜백을 받는 함수가 언제 호출할지 결정합니다.
-> 즉, 호출 시점의 제어권은 콜백을 받는 함수가 가집니다.
setTimeout(function () {
console.log("1초 후 실행");
}, 1000);
여기서 콜백 함수는 우리가 호출하는 게 아니라, setTimeout이 1초 후에 호출 합니다.
2. 인자의 제어권
콜백 함수가 호출될 때 어떤 인자가 전달될지도 콜백을 받는 함수가 결정합니다.
[1, 2, 3].forEach(function (num, index) {
console.log(num, index);
});
forEach는 콜백 함수에 배열 요소 값, 인덱스 등 인자를 자동으로 넘겨줍니다.
3. this의 제어권
콜백 함수의 this가 무엇을 가리키는지는 상황마다 달라지며, 콜백을 호출하는 함수가 this를 바꿀 수도 있습니다.
const obj = {
value: 42,
method(callback) {
callback();
}
};
obj.method(function () {
console.log(this); // 일반 함수 호출 → this는 글로벌(또는 undefined, strict 모드)
});
하지만 call, apply, bind 등을 사용해 명시적으로 this를 지정할 수도 있습니다.
function callback() {
console.log(this);
}
callback.call({ a: 1 }); // { a: 1 }
콜백함수_객체의 메서드 전달 시 this
다음과 같은 obj 객체에 logValues라는 메서드가 있습니다.
var obj = {
vals: [1, 2, 3],
logValues: function(v, i) {
console.log(this, v, i);
}
};
아래와 같이 작성하면, obj가 직접 logValues를 호출하는 것입니다.
-> 그래서 this는 obj를 가리킵니다.
obj.logValues(1, 2);
하지만 다음과 같이 forEach로 작성하면 this는 obj가 아닙니다.
[4, 5, 6].forEach(obj.logValues);
여기서는 obj.logValues 함수 자체만 전달하고 있습니다.
콜백으로 함수만 넘기면, 그 함수는 메서드가 아니라 그냥 함수로 실행됩니다.
즉, forEach는 obj과 전혀 상관없이 저 함수를 그냥 함수로 호출 합니다.
callback(value, index, array);
이 순간, this는 undenfined(또는 글로벌 객체)로 나옵니다. 왜냐하면 obj가 호출한 게 아니기 때문입니다.
콜백함수_내부 this에 다른 값 바인딩
<전통적 방식>
전통적 방식으로는 함수 내부에 self 변수를 두어 객체 자신을 넣도록 아래와 같이 self = this 식으로 선언하는 방식이 있습니다.
하지만 실제로는 this를 사용하게 아니기도 하고, 번거롭습니다.
var obj1 = {
name: 'obj1',
func: function() {
var self = this; //이 부분!
return function () {
console.log(self.name);
};
}
};
// 단순히 함수만 전달한 것이기 때문에, obj1 객체와는 상관이 없어요.
// 메서드가 아닌 함수로서 호출한 것과 동일하죠.
var callback = obj1.func();
setTimeout(callback, 1000);
그래서 다음과 같이 수정합니다.
var obj1 = {
name: 'obj1',
func: function() {
var self = this; //이 부분!
return function () {
console.log(self.name);
};
}
};
// ---------------------------------
// obj1의 func를 직접 아래에 대입해보면 조금 더 보기 쉽습니다!
var obj2 = {
name: 'obj2',
func: obj1.func
};
var callback2 = obj2.func();
setTimeout(callback2, 1500);
// 역시, obj1의 func를 직접 아래에 대입해보면 조금 더 보기 쉽습니다!
var obj3 = { name: 'obj3' };
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000);
그런데 여기서 obj2에서 this를 바인딩 하는 방식은 위에서 했던 obj1과 동일하게 작동합니다. 단지 이건 self = this 패턴의 의도를 강조하기 위해 일부러 넣은거 같습니다.
3번째 obj3에서 call 메서드를 이용한 강제 바인딩하는 방식이 좀 더 다르게 보여집니다.
<가장 좋은 방법 -> bind 메서드의 활용>
가장 좋은 방법은 bind 메서드를 이용한 방식이 가장 좋다고 합니다. 그런데 call이란 문법적으로 똑같아 보입니다.
var obj1 = {
name: 'obj1',
func: function () {
console.log(this.name);
}
};
//함수 자체를 obj1에 바인딩
//obj1.func를 실행할 때 무조건 this는 obj1로 고정해줘!
setTimeout(obj1.func.bind(obj1), 1000);
var obj2 = { name: 'obj2' };
//함수 자체를 obj2에 바인딩
//obj1.func를 실행할 때 무조건 this는 obj2로 고정해줘!
setTimeout(obj1.func.bind(obj2), 1500);
하지만 call과 bind는 사용 목적과 바인딩 시점이 다르다고 합니다.
메서드 | 목적 | this 바인딩 시점 | 반환값 | 사용 예시 |
call | 즉시 함수 실행 + this 바인딩 | 즉시 | 함수의 실행 결과 | 바로 실행할 때 |
bind | 함수 내부의 this를 영구 고정(새 함수 반환) | 나중(호출 시) | 새로운 함수(함수 자체) | 나중에 호출할 함수 준비 |
function show() {
console.log(this.name);
}
const obj = { name: 'Alice' };
show.call(obj); // 즉시 실행 → Alice
const boundFunc = show.bind(obj); // 새 함수 반환 (아직 실행 안 됨)
boundFunc(); // 나중에 실행 → Alice
그래서 call은 함수는 그대로 두고, 그때그때 내가 원하는 this를 넘겨서 한 번 호출하는데 쓰이고, bind는 아예 this를 고정한 새로운 함수를 만들고 계속 재사용 가능하는 방식입니다.
콜백 함수_Promise
Promise는 비동기 처리에 대해, 처리가 끝나면 알려달라는 약속입니다.
- new 연산자로 호출한 Proimse의 인자로 넘어가는 콜백 함수는 바로 실행
- 그 내부의 resolve(또는 reject) 함수를 호출하는 구문이 있을 경우 resolve(또는 reject) 둘 중 하나가 실행되기 전까지는 다음, 오류로 넘어가지 않음
- 따라서, 비동기 작업이 완료될 때 비로소 resolve, reject 호출
그래서 이 방법을 이용해 비동기를 동기적 표현으로 구현 할 수 있습니다.
콜백 함수_Generator 및 async/await
Generator
함수 실행을 일시 중지/재개할 수 있는 특수한 함수입니다. 주로 비동기 흐름을 순차적으로 작성할 때 사용합니다.
특징
- function* 키워드로 선언
- yield 키워드로 중간에 멈췄다가, .next()로 이어서 실행
- Promise와 함께 사용해서 비동기 흐름을 제어할 수 있음
- 복잡하지만 Promise 기반 라이브러리(co 등)에서 종종 사용했음.
function* generator() {
yield "First";
yield "Second";
return "Done";
}
const gen = generator();
console.log(gen.next()); // { value: "First", done: false }
console.log(gen.next()); // { value: "Second", done: false }
console.log(gen.next()); // { value: "Done", done: true }
function* asyncGenerator() {
const result = yield fetch("https://api.example.com");
console.log(result);
}
async/await
Promise 기반 비동기 처리를 가장 간단하고 직관적으로 만드는 문법
특징
- async 키워드로 함수를 선언하면 무조건 Promise 반환
- await 키워드로 Promise가 해결될 때까지 기다림.
- 마치 동기 코드처럼 작성 가능 -> 가독성 최고
- 사실 내부적으로 Generator 함수 + Promise 조합과 비슷한 구조
async function fetchData() {
const result = await new Promise((resolve) => {
setTimeout(() => {
resolve("Data loaded");
}, 1000);
});
console.log(result);
}
fetchData();
'이노베이션캠프 > TIL' 카테고리의 다른 글
이노베이션 캠프 6일차_JSON 데이터가 정상 로드 안 되는 현상 (0) | 2025.07.12 |
---|---|
이노베이션 캠프 5일차_무한 루프 로비 화면 (0) | 2025.07.11 |
이노베이션 캠프 4일차_MongoDB, 미들웨어, Joi, Prettier (1) | 2025.07.10 |
이노베이션 캠프 3일차_Node.js 기초 개념 정리 (3) | 2025.07.09 |
이노베이션 캠프 1일차_3주차 강의 정리 (0) | 2025.07.07 |