JavaScript - 콜백 함수
자바스크립트 콜백 함수에 대해서 가볍게 공부하고, 프로미스를 공부하려 했는데 콜백 함수에 대해서 더 정확히 알지 못하면 의미가 없다고 느껴서 콜백 함수를 정리합니다.
@먼저 비동기 처리와 동기 처리에 대해서 알아봅시다.
-동기 처리
말 그대로 실행하는 순서대로 흘러가는 로직을 말합니다.
function sync1() {
console.log('test1');
}
sync1();
console.log('test2');
// 출력 결과
// test1;
// test2;
위의 처리 처럼 출력 결과가 순서대로 실행이 됩니다. 이것을 동기 처리라고 합니다.
-비동기 처리
자바스크립트의 대표적인 특징 중 하나가 비동기 처리에 특화 되었다고 할 수 있습니다.
서버와의 통신 중에 어떤 함수의 처리가 길어지면, 브라우저는 멈춤 상태가 되어 유저가 화면을 볼 수 없습니다.
그 외에도 대량의 데이터를 가져와서 게시판에 표시하고 싶은데, 해당 처리의 연산 속도가 늦어지면, 그 뒤의 처리로 넘어갈 수 없습니다. 그래서 이러한 것은 비동기 처리로 해주어야 합니다.
비동기 처리의 예시로는 대표적으로 ajax 와 setTimeout() 이라는 Web API(브라우저 내장 함수)가 있습니다.
여기서는 setTimeout() 을 활용하여 예를 들어보겠습니다.
console.clear();
console.log('logic 1');
setTimeout(function () {
console.log('hi');
}, 1000);
console.log('logic 2');
// 출력 결과
// logic 1
// logic 2
// hi
위의 출력 결과를 보면 hi 라는 글자가 가장 나중에 나오는 것을 볼 수 있습니다.
setTimeout 함수를 활용하여 1초의 딜레이를 걸었기 때문입니다.
즉, setTimeout 을 이용해서 하고 싶은 처리를 비동기를 걸어놓고, 나머지 다른 처리를 수행할 수 있는 것입니다.
@비동기 처리의 문제점
그러면 비동기 처리의 문제점에 대해서 살펴보겠습니다.
간단하게 말하자면, 비동기 처리는 결과 값을 기다리지 않는 것에 있습니다.
console.log('logic 1');
function getData() {
let temp;
setTimeout(function () {
temp = 'logic 2';
}, 1000);
return temp;
}
console.log(getData());
console.log('logic 3');
위 로직의 출력 결과는 아래와 같습니다.
logic 1
undifined
logic 3
getData() 함수를 실행했지만, 해당 함수 안에서는 2초의 딜레이를 걸고 있습니다.
따라서, getData() 함수가 최종적으로 실행하는 return temp; 를 console.log(getData()) 에서 인식 할 수 없습니다.
즉, 저 상태에서는 어떠한 방법을 써봐도 function getData(){} 코드의 밖에서 temp 결과 값을
엑세스 할 수 있는 방법이 없는 것 입니다.
데이터를 받아서 그 밑에 연관있는 작업을 해야하는데, 이럴 때는 문제가 됩니다.
@콜백 함수
위와 같은 문제점을 해결하기 위해서 존재하는 것이 바로 콜백 함수 입니다.
콜백 함수를 사용하면 해당 처리가 끝날 때 까지 기다려주면서 나머지 코드를 실행하고, 처리가 끝나면 알려주게 됩니다.
아래는 비동기 처리의 문제점이 있는 코드입니다.
console.clear();
console.log('1. 화면 구성 메서드');
function getUser() {
let user;
setTimeout(() => {
user = { name: 'test', pw: '123' };
});
return user;
}
console.log('2. 유저 데이터 받아와서 해야하는 처리들');
console.log(getUser());
console.log('3. 나머지 다른 처리');
// 출력 결과
1. 화면 구성 메서드
2. 유저 데이터 받아와서 해야하는 처리들
undefined
3. 나머지 다른 처리
이것을 콜백 함수로 바꾸면 아래와 같이 됩니다.
console.log('1. 화면 구성 메서드');
function getUser(callback) {
let user;
setTimeout(() => {
user = { name: 'test', pw: '123' };
return callback(user);
}, 2000);
}
console.log('2. 유저 데이터 받아와서 해야하는 처리들');
getUser(console.log);
console.log('3. 나머지 다른 처리');
// 출력 결과
// 1. 화면 구성 메서드
// 2. 유저 데이터 받아와서 해야하는 처리들
// 3. 나머지 다른 처리
// {name: 'test', pw: '123'}
간단히 설명을 하자면 getUser 함수를 호출 하면서 인자 값으로 console.log 를 던졌습니다.
그리고 getUser 함수를 선언한 데서 callback 이라는 인자 값을 받고 있죠.
즉, console.log 와 callback 은 똑같습니다.
console.log 는 Web API 로 브라우저에 내장되어있는 API 이며, 브라우저 메모리에 특정 주소 값을 가지고 있습니다.
callback 이라는 인자도 같은 주소 값을 바라보게 되면서, console.log 와 같은 기능을 할 수 있는 것입니다.
그래서 getUser(console.log) 의 결과 값은 undefined 가 되는 것이 아니라,
던져준 console.log 라는 함수에 user 라는 객체를 넣어서 출력을 하게 되어서 2초 후 정상적으로 출력이 되는 것입니다.
즉, 콜백 함수는 "데이터가 준비된 시점에서 다시 불러줘" 라고 예약을 거는 것 입니다.
또 다른 예제로 살펴보겠습니다.
// 상품정보 획득 메서드
function getProducts(callback) {
let products = new Array();
setTimeout(function () {
products.push({ product_name: '에어조던1', price: '10,000' });
products.push({ product_name: '에어조던2', price: '50,000' });
callback(products);
}, 3000);
}
// 메서드 실행
getProducts((result) => {
let div_product = document.querySelector('.product');
div_product.textContent = div_product.textContent + result[0].product_name;
console.log(result);
});
getProducts 라는 함수를 선언했는데 인자 값이 callback 이라고 되어있습니다.
사실 인자의 이름을 callback 이라고 주지 않아도 됩니다. 개발자 마음대로 정할 수 있지만,
"저 인자 값은 함수야" 라는 것을 알 수 있게 끔 적어주는 것이 가장 좋다고 생각합니다.
(여기서는 예를 들기 위해서 callback 이라고 적어주었습니다.)
그러면 저 callback 은 뭘까요?
바로 getProducts 안의 함수로 선언된 부분을 뜻 합니다.
(result) => {
let div_product = document.querySelector('.product');
div_product.textContent = div_product.textContent + result[0].product_name;
console.log(result);
}
여기서는 Arrow function 을 사용했기 때문에, Arrow function 에 익숙하지 않다면 직관적이지 않을 수 있습니다. 사실 저도 이번에 공부하면서 처음 써봤습니다..
저 함수 선언을 좀 더 쉽게 풀어 쓴다면 아래와 같이 쓸 수 있습니다.
getProducts(function(result){
// 로직
});
이걸 다시 더 풀어서 직관적으로 쓰고 싶다 한다면 아래와 같습니다.
(하지만 이렇게 하면 ES6의 매력이 사라지는 코드가 됩니다.. 라고 생각합니다)
function test(result){
let div_product = document.querySelector('.product');
div_product.textContent = div_product.textContent + result[0].product_name;
console.log(result);
}
getProducts(test);
이쯤에서 한 참 이해가 안되던 부분이 있습니다.
Arrow function 을 사용하던, 좀 더 풀어서 메서드를 선언하던, 도대체 저 result 라는 인자 값은 뭘까요?
그리고 마지막 test 함수를 넘기는 분에서는 왜, 도대체, 어째서 인자 값을 넘기지 않는 것일까요?
getProducts(test).. 라니.. 분명히 test() 함수는 인자 값을 하나 받고 있는데 말이죠..
사실 이건 잘 모르겠습니다.. 그냥 그렇게 쓴다고 이해하고 넘어갔습니다.
(console.log 를 인자값으로 전달하는 것과 같은 개념이라 생각하고 있습니다.)
실제로 getProducts(test(result)) 라고 쓰면 에러가 납니다.
다시 본론으로 돌아와서 result 라는 녀석은, getProducts 함수를 선언하는 곳에서 처리하고 있는
products 오브젝트 입니다.
// 상품정보 획득 메서드
function getProducts(callback) {
let products = new Array();
setTimeout(function () {
products.push({ product_name: '에어조던1', price: '10,000' });
products.push({ product_name: '에어조던2', price: '50,000' });
callback(products);
}, 3000);
}
// 메서드 실행
getProducts((result) => {
let div_product = document.querySelector('.product');
div_product.textContent = div_product.textContent + result[0].product_name;
console.log(result);
});
풀어서 이야기 하자면,
1. getProducts 메서드를 실행합니다.
2. 인자 값으로는 Arrow function 메서드를 줍니다.
3. 그리고 Arrow function 메서드는 result 라는 인자 값이 있는 것입니다.
4. 그러면 getProducts 메서드의 선언부에 가서 그 안에 있는 처리를 실행합니다.
5. 그런데 그 실행은 3초 후에 이루어지고, 3초 후에 products 라는 배열에 값을 넣고
6. callback(products) 를 실행시킵니다.
7. callback(products)은, Arrow function 과 같습니다.
8. Arrow function 은 아래와 같이 쓸 수 있다고 했죠.
function test(result){
let div_product = document.querySelector('.product');
div_product.textContent = div_product.textContent + result[0].product_name;
console.log(result);
}
getProducts(test);
여기까지 콜백 함수가 작동하는 방식을 알아보았습니다.
그러면 이제 다음 포스팅에서 콜백 지옥이라는 것에 대해서 알아보겠습니다.