콜백함수(callback function)

yeun·2022년 10월 26일
1
post-thumbnail

🐕 콜백함수(callback function)는 다른 코드의 인자로 넘겨주는 함수이다.
콜백 함수를 넘겨받은 코드는 이 콜백함수를 필요에 따라 적절한 시점에 실행할 것이죠!

콜백함수에 대해 알아보기 전에 비동기와 동기에 대해서 간략하게 알아보고 넘어가겠습니다!

비동기와 동기🍜🍜

  • 동기적(Synchronous)
    현재 실행중인 코드가 완료된 후에야 다음코드를 실행하는 방식
    ex. 짜장면을 배달 → 다 먹을때까지 기다림 → 짜장면집으로 돌아간다
console.log('배달 출발');
console.log('배달 완료!');
console.log('손님이 짜장면을 다 먹기를 기다린다...');
console.log('1시간 후...');
console.log('그릇을 찾은 후 가게로 돌아온다.');
  • 비동기적(Asychronous)
    코드는 현재 실행중인 코드의 완료여부와 무관하게 즉시 다음으로 넘어간다.
    cpu의계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적인 코드
    ex. 짜장면을 배달 → 손님이 먹는동안 다른일 하러 떠남 → 다 먹은 뒤 그릇을 찾아서 돌아감
console.log('배달 출발');
console.log('배달 완료!');
setTimeout(() => {
	console.log('1시간 후...');
	console.log('그릇을 찾은 후 가게로 돌아온다.');
}, 1000);
console.log('가게로 돌아간다.');
console.log('설거지를 한다..');
console.log('서빙을 한다.');
console.log('밥을 먹는다.');
console.log('후식을 먹는다.');

위 코드가 비동기적 코드가 될 수 있는 이유는 무엇일까요? 바로 setTimeout(func, delay) 때문!

여기서 인자로 받는 func가 우리가 공부하고 있는 callback Function이다.



callback이란? (되돌아와서 호출해라! 라는 명령이다.)

  • printArray는 콜백함수고 clearInterval에게 제어권을 넘겨주었기 때문에
    printArray가 아니라 setInterval이 호출시점을 결정하게 됨.
const arr = ['무', '야', '호'];

const printArray = () = {
	console.log(arr.shift());
	if(!arr.length) {
		clearInterval(timer);
	}
};

const timer = setInterval(printArray, 1000);


이렇게 편리한 callback이 꼭 아름답지 만은 않은 이유는 무엇일까요?

바로 콜백 지옥(callback Hell)🔥때문!

setTimeout(function(name){
    var coffeeList = name;
    console.log(coffeeList);

    setTimeout(function(name){
        coffeeList += ', ' + name;

        setTimeout(function(name){
            coffeeList += ', ' + name;

            setTimeout(function(name){
                coffeeList += ', ' + name;
            }, 500, 'cafeLatte');
        }, 500, 'cafeMocha');
    }, 500, 'americano');
}, 500, 'espresso');

위 코드는 각 callback이 커피이름을 전달하고 이름을 추가해주는 코드입니다.
목적달성에는 지장이 없지만 들여쓰기 수준이 과도하게 깊어졌을 뿐만 아니라
순서가 아래에서 위로 향하고 있어 어색하게 느껴집니다..

⇒ 가독성문제와 어색함을 동시에 해결하기 위하여 익명의 콜백함수를 모두 기명함수로 바꿔봄

var coffeeList = '';

var addEspresso = function(name){
    coffeeList = name;
    console.log(coffeeList);
    setTimeout(addAmericano, 500, 'americano');
};

var addAmericano = function(name){
    coffeeList += ', ' + name;
    console.log(coffeeList);
    setTimeout(addMocha, 500, 'cafeMocha');
};

var addMocha = function(name){
    coffeeList += ', ' + name;
    console.log(coffeeList);
    setTimeout(addLatte, 500, 'cafeLatte');
};

var addLatte = function(name){
    coffeeList += ', ' + name;
    console.log(coffeeList);
};

setTimeout(addEspresso, 500, 'espresso');

이 방식은 코드의 가독성을 높일뿐 아니라 함수 선언과 함수 호출만 구분할 수 있다면 위에서 아래로 순서대로 읽어내려가는데 어려움이 없다.

하지만... 일회성 함수를 모두 변수에 할당하는 것이 마뜩찮을 수도?



그래서 ES6에서는Promise , Generator가 나왔지롱

new Promise(function(resolve) {
    setTimeout(function() {
        var name = 'espresso';
        console.log(name);
        resolve(name);
    }, 500);
}).then(function(prevName) {
    return new Promise(function(resolve){
        setTimeout(function() {
            var name = prevName + ', americano';
            console.log(name);
            resolve(name);
        }, 500)
    });
}).then(function (prevName){
    return new Promise(function (resolve){
        setTimeout(function() {
            var name = prevName + ', cafeMocha';
            console.log(name);
            resolve(name);
        }, 500);
    });
}).then(function(prevName) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            var name = prevName + ', cafeLatte';
            console.log(name);
            resolve(name);
        }, 500);
    });
});
var addCoffee = function(name) {
	return function(preName) {
		return new Promise(function (resolve) {
			setTimeout(function() {
				var newName = preName ? (preName + ', ' + name) : name;
				console.log(newName);
				resolve(newName);
			}, 500);
		});
	};
};
addCoffee('에스프레소')()
	.then(addCoffee('아메리카노'))
	.then(addCoffee('카페모카'))
	.then(addCoffee('카페라떼'));

출력결과



여기서 잠깐.. 😅 프로미스가 몬데..!!!

🐕 Promise란..?
ES6에서 추가된 JavaScript 내장 객체
프로미스란 자바스크립트 비동기 처리에 사용되는 객체
resolve는 성공, reject는 실패를 반환한다!



왜 필요한가?

  • 주로 서버에서 받아온 데이터를 화면에 표시할때 사용한다.

  • 일반적으로 웹 애플리케이션을 구현할 때 서버에서 데이터를 요청하고 받아오기 위하여 사용..

  • 실제 연산을 직접 처리해주는 것은 아니고, 해당 연산을 대리하여 결과나 실패를 처리하기 위한 처리기와 연결할 수 있도록 하는 객체

  • new Promise()의 3가지 상태

    • Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
    • Fulfilled(이행) : 비동기 처리가 완료되어 프로미스 결과 값을 반환해준 상태
    • Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태




대기(Pending)

먼저 아래와 같이 new Promise() 메서드를 호출하면 대기(Pending)상태가 된다.

new Promise();

new Promise()를 호출할때 콜백함수를 선언할 수 있고, 콜백함수의 인자는 resolve , reject 이다.

new Promise(function(resolve, reject) {

});


Fulfilled(이행) = 완료

여기서 콜백함수의 인자 resolve 를 아래와 같이 실행하면 이행(Fulfilled)상태가 된당.

new Promise(function(resolve, reject) {
	resolve();
});

그리고 이행 상태가 되면 아래와 같이 then()을 이용하여 처리 결과 값을 받을 수 있다!

function getData(){
	return new Promise(function(resolve, reject) {
		var data = 100;
		resolve(data);
	});
}

//resolve()의 결과값 data를 resolvedData로 받음
getData().then(function(resolvedData){
		console.log(resolveData); //100
});


Rejected(실패)

new Promise()로 프로미스 객체를 생성하면 콜백 함수 인자로 resolvereject를 사용할 수 있다고 했다. 여기서 reject를 아래와 같이 호출하면 실패 상태가 된다!

new Promise(function(resolve, reject) {
	reject();
});

그리고, 실패상태가 되면 실패한 이유(실패 처리의 결과 값)을 catch()로 받을 수 있음!

function getData() {
    return new Promise(function(resolve, reject) {
      reject(new Error("Request is failed"));
    });
  }
  
  // reject()의 결과 값 Error를 err에 받음
  getData().then().catch(function(err) {
    console.log(err); // Error: Request is failed
  });


Generator

또 다른 비동기 작업의 동기적 표현으로 Generator가 있다.

var addCoffee = function (preName, name){
    setTimeout(function () {
        coffeeMaker.next(preName ? preName + ', ' + name : name);
    }, 500);
};
var coffeeGenerator = function* () {
    var espresso = yield addCoffee('', '에스프레소');
    console.log(espresso);
    var americaon = yield addCoffee(espresso, '아메리카노');
    console.log(americaon);
    var mocha = yield addCoffee(americaon, '카페모카');
    console.log(mocha);
    var latte = yield addCoffee(mocha, '카페라떼');
    console.log(latte);
};

var coffeeMaker = coffeeGenerator();
coffeeMaker.next();

위 코드에서 6번째 줄 ‘*’가 붙은 함수가 바로 Generator함수
Generator를 실행하면 Iterator가 반환되는데 Iteratornext라는 메서드를 가지고 있다.
next를 호출하면 Generator 함수 내부에서 가장 먼저 등장하는 yield에서 함수의 실행을 멈추게 되고, 이후 다시 next메서드를 호출하면 앞서 멈췄던 부분 부터 시작해서 그 다음에 등장하는 yield에서 함수의 실행을 멈춥니다.

⇒ 비동기 작업이 완료되는 시점마다 next메서드를 호출해 준다면 Generator함수 내부의 소스가 위에서 부터 아래로 순차적으로 진행된다.




🐕총정리

  • 콜백함수는 다른 코드에 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다.

  • 제어권을 넘겨받은 코드는 다음과 같은 제어권을 가진다.
    1) 콜백함수를 호출하는 시점을 스스로 판단해서 실행합니다.
    2) 콜백함수를 호출할 때 인자로 넘겨줄 값들 및 그 순서가 정해져 있습니다. 이 순서를 따르지 않고 코드를 작성하면 엉뚱한 결과를 얻게 됩니다.
    3) 콜백함수의 this가 무엇을 바라보도록 할지가 정해져 있는 경우도 있습니다. 정하지 않은 경우에는 전역 객체를 바라봅니다. 사용자 임의로 this를 바꾸고 싶을 경우 bind메서드를 활용하면 됩니다.

  • 어떤 함수에 인자로 메서드를 전달하더라고 이는 결국 함수로서 진행됩니다.

  • 비동기 제어를 위해 콜백함수를 사용하다 보면 콜백지옥에 빠지기 쉽습니다. 최근의 ECMAScript에는 Promise, Generator, async/awit 등 콜백 지옥에서 벗어날 수 있는 훌륭한 방법들이 속속 등장하고 있다!
profile
Frontend React w/based in S.Korea. Interested in UX/FE.

0개의 댓글