자바스크립트 이론 - 콜백

콜백

시작

이어서 콜백함수에 대해 알아보겠습니다.

콜백 함수란?

다른 코드에 인자로 넘겨주는 함수, 콜백 함수를 넘겨받은 코드는 이 콜백 함수를 필요에 따라 적절한 시점에 실행할 수 있다. setTimeout, forEach 등에서 사용할 수 있다.
callback은 call(부르다) + back(되돌아오다)의 조합 즉, 콜백 함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨주어 그 제어권도 함께 위임한 함수이다. 콜백 함수를 위임받은 코드는 자체적으로 내부 로직에 의해 이 콜백 함수를 적절한 시점에 수행한다.

제어권

호츨 시점

js
var count = 0;
//timer : 콜백 내부에서 사용할 수 있는 '어떤 게 돌고있는지' 알려주는 id값
var timer = setInterval(function() {
  // setInterval(콜백함수, 시간타이머) 시간 마다 콜백함수 호출(실행)
	console.log(count);
	if(++count > 4) clearInterval(timer); // Interval stop
}, 300);

// 0 (0.3sec)
// 1 (0.6sec)
// 2 (0.9sec)
// 3 (1.2sec)
// 4 (1.5sec)

아래도 같은 코드이다.

js
var count = 0;
var cbFunc = function () {
	console.log(count);
	if (++count > 4) clearInterval(timer);
};
var timer = setInterval(cbFunc, 300);
// 호출 주체 및 제어권은 setInterval이 쥐게 된다.

// 실행 결과
// 0 (0.3sec)
// 1 (0.6sec)
// 2 (0.9sec)
// 3 (1.2sec)
// 4 (1.5sec)

인자로 전달

js
//Array.prototype.map(callback[, thisArg])
//-> this 생략하면 전역객체 바인딩
//callback: function(currentValue, index, array)
//map : 배열의 모든 요소를 꺼내어 처음부터 끝까지 콜백함수 반복호출 + 새로운 배열 리턴

var newArr = [10, 20, 30].map(function (currentValue, index) {
	console.log(currentValue, index);
	return currentValue + 5;
});
console.log(newArr); // 15 25 35

//자, 그럼 콜백함수의 인자 2개의 위치가 바뀐다면 어떻게 될까요?

아래는 위 예시사항을 바꾼 결과입니다.

js
var newArr = [10, 20, 30].map(function (index, currentValue) {
	console.log(currentValue, index);
	return currentValue + 5;
});
console.log(newArr); // 5 6 7

이를 보면 인자는 명이 아닌 순서가 중요하다는 것을 볼 수 있다. 콜백함수가 호출 될 때 요구사항을 잘 볼 것. 제어권이 넘어갈 map함수의 규칙에 맞게 호출해야 한다.

this

함수로서의 호출은 method() 메서드로서의 호출은 obj.method()이다. 콜백 함수도 함수이기 때문에 기본적으로는 this가 전역객체를 참조한다.
제어권을 넘겨받을 코드에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조한다.

js
// Array.prototype.map을 직접 구현해봤어요!
Array.prototype.map = function(callback, thisArg) {
	var mappedArr = [];
  // this 는 현재 배열의 갯수.
	for (var i = 0; i < this.length; i++) {
    // || 연산으로 true인 경우 해당 값 할당
		var mappedValue = callback.call(thisArg || window
																			, this[i], i, this);
		mappedArr[i] = mappedValue;
	}
	return mappedArr;
};

// 이제 this에 다른 값이 담기는 이유를 알 수 있겠죠?
// [1, 2, 3].map(function() { ~~~~~ }, this);
js
//이젠 이 코드를 좀 더 잘 이해할 수 있어요!!

//setTimeout은 내부에서 콜백 함수를 호출할 때, call 메서드의 첫 번째 인자에
//전역객체를 넘겨요
//따라서 콜백 함수 내부에서의 this가 전역객체를 가리켜요
setTimeout(function() { console.log(this); }, 300); // Window { ... }

//forEach도 마찬가지로, 콜백 뒷 부분에 this를 명시해주지 않으면 전역객체를 넘겨요!
[1, 2, 3, 4, 5].forEach(function (x) {
	console.log(this); // Window { ... }
});

//addEventListener는 내부에서 콜백 함수를 호출할 때, call 메서드의 첫 번째
//인자에 addEventListener메서드의 this를 그대로 넘겨주도록 정의돼 있어요(상속)
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
		.addEventListener('click', function(e) {
	console.log(this, e);
});

콜백 함수도 함수

콜백 함수로 어떤 객체의 메서드를 전달하더라도, 그 메서드는 메서드가 아닌 함수로서의 호출

js
var obj = {
	vals: [1, 2, 3],
	logValues: function(v, i) {
		console.log(this, v, i);
	}
};

//method로써 호출
obj.logValues(1, 2);

//callback => obj를 this로 하는 메서드를 그대로 전달한게 아니에요
//다닞, obj.logValues가 가리키는 함수만 전달한거에요(obj 객체와는 연관이 없습니다)
[4, 5, 6].forEach(obj.logValues);

콜백 함수 내부의 this에 다른 값 바인딩 하기

js
var obj1 = {
	name: 'obj1',
	func: function () {
		console.log(this.name);
	}
};

//함수 잡채를 obj1에 바인딩
setTimeout(obj1.func.bind(obj1), 1000);
// obj1 (1.0 sec)

var obj2 = { name: 'obj2' };
//함수 잡채를 obj2에 바인딩
setTimeout(obj1.func.bind(obj2), 1500);
// obj2 (1.5 sec)

콜백 지옥과 비동기 제어

콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 보기 힘든 경우를 콜백 지옥이라 할 수 있겠다.

js
//0.5초 주기마다 커피 목록을 수집하고 출력
//각 콜백은 커피 이름을 전달하고 목록에 이름을 추가
//문제점 : 들여쓰기 ㄷㄷ, 값 전달의 순서가 아래 -> 위

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

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

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

			setTimeout(function (name) {
				coffeeList += ', ' + name;
				console.log(coffeeList);
			}, 500, '카페라떼');
		}, '카페모카');
	}, '아메리카노');
}, '에스프레소');

동시와 비동기

동기(synchronous)는 현재 실행중인 코드가 끝나야 다음 코드를 실행하는 방식이고 비동기(asynchronous)는 실행 중인 코드 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식이다. setTimeout, addEventListener가 비동기 방식이다.

콜백 지옥 해결 방법 - 기명 함수 전환

js
var coffeeList = '';

var addEspresso = function (name) {
	coffeeList = name;
	console.log(coffeeList);
	setTimeout(addAmericano, 500, '아메리카노');
};

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

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

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

// 0.5s 에스프레소
// 1.0s 에스프레소, 아메리카노
// 1.5s 에스프레소, 아메리카노, 카페모카

setTimeout(addEspresso, 500, '에스프레소');

//가독성 굳
//위 -> 아래
//흠... 근데, 한 번만 쓸건데 이렇게 이름을 다 붙여야 한다고?
//위 코드는 근본적인 해결책은 아닌 것 같아 -> 비동기 작업의 동기적 표현이 필요해..!!

콜백 지옥 해결 방법 - 비동기 작업의 동기적 표현 - Promise

js