이노베이션캠프/Js 문법 종합반

[TIL] 4주차 : callback 함수

개린이다 2023. 5. 25. 18:54

* 콜백함수_기본개념

 

1. 콜백함수란?

- 다른 코드의 인자로 넘겨주는 함수!

- 콜백함수를 넘겨받은 주체는 이 콜백함수를 필요에 따라 적절한 시점에 실행하게 됨(제어권이 그들에게 있는 것)

- 즉, 제어권을 넘겨줄테니 너가 알고 있는 그 로직으로 처리해줘! 

=> 마지막 정리

즉, 콜백함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수. 콜백함수를 위임받은 코드는 자체적으로 내부 로직에 의해 이 콜백함수를 적절한 시점에 실행 <- 이 적절한 시점 또한 제어권이 있는 위임받은 코드가 알아서 할거임 

 

2. 콜백함수_제어권

- 도대체 콜백함수를 넘겨받은 주체 가 가진 제어권이란 무엇인가?

1) 호출 시점에 대한 제어권을 갖는다

var count = 0;
var cbFunc = function () {
	console.log(count);
	if (++count > 4) clearInterval(timer);
};
var timer = setInterval(cbFunc, 300);

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

- setInterval : 반복해서 매개변수로 받은 콜백함수의 로직을 수행

- 0.3초에 대한 제어권을 setInterval이 가지고 있음. 왜? 매개변수는 function()~ clearInterval(timer);} 까지임. 이 func에 대한 제어권을 setInterval이 가지고서 0.3초마다 실행해줘 해서 실행이 되는 거임

 

- 간단하게 표로 정리해보면

- 원래 cbFunc()를 수행하면, 그 호출주체제어권은 모두 사용자임

- setInterval 로 넘겨주게 되면 그 호출주체제어권은 모두 setInterval이 됨

code 호출 주체 제어권
cbFunc(); 사용자 사용자
setInterval(cbFunc, 300); setInterval setInterval

 

2) 인자에 대한 제어권을 갖는다.

- map 함수로 예시를 들어보자

map 함수 : 각 배열 요소를 변환하여 새로운 배열을 반환. 기존 배열을 변경하지 않고, 새로운 배열을 생성! 함!

// map 함수에 의해 새로운 배열을 생성해서 newArr에 담고 있네요!
var newArr = [10, 20, 30].map(function (currentValue, index) {
	console.log(currentValue, index);
	return currentValue + 5;
});
console.log(newArr);

// -- 실행 결과 --
// 10 0
// 20 1
// 30 2
// [ 15, 25, 35 ]

// return 을 안해주면
// [undefined, undefined, undefined] 가 나옴 주의!

- index , currentValue : 사람이 이해할 수 있는 변수명일 뿐!

- 컴퓨터가 인식할 수 있는 건, 매개변수가 어디에 있는지, 자리위치라는 거임

- index, currentValue 의 이름은 자유롭게 바꿔도 상관이 없다는 거임............

- 그러니까 원하는 결과를 뽑아내기 위해서는 명세서 대로 사용방법대로 사용해야함. 컴퓨터는 바보니까..

- 즉, 인자에 대한 제어권은 누구에게 있나? 사람에게 없음. 오직 함수(여기서는 map)만이 가지고 있는 거임. 우리 마음대로 안됨!!!!!

 

3) this 바인딩

- 콜백함수도 함수이기 때문에 기본적으로 this가 전역객체를 참조한다

- 여기서 예외가 있었음! 제어권을 넘겨받을 코드에서 콜백함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조한다! 

이 예외가 되는 세가지 케이스를 알아보도록 하자..

 

핵심은 call, apply !

//이젠 이 코드를 좀 더 잘 이해할 수 있어요!!

// 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);
});

 

3. 콜백함수는 함수다

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

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);

 

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

1) 전통적인 해결

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);

2) 전통해결 아닌, 좋은 해결방법 : bind 메서드 이용

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);

5. 콜백 지옥과 비동기 제어

1) 콜백지옥

매개변수에 계속 함수가 들어가는 거임. 그러면 계속 들여쓰기, 들여쓰기, 들여쓰기..

(출처 : https://preiner.medium.com/callback지옥에-promise-적용하기-d02272ecbabe )

- 가독성이 정말 안 좋고, 유지보수 측면에서 정말 안 좋음. ㅜㅜ

 

2) 동기vs비동기

출처 : https://smallzoodevs-organization.gitbook.io/copy-of-javascript-study/day-05./1.

위 : 동기 / 아래 : 비동기(먼저 만들어진 건 진동벨을 통해 먼저 가져가는)

(1) 동기 : synchronous

- 현재 실행중인 코드가 끝나야 다음 코드로 넘어감

- CPU의 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적 코드(오래걸린다고 동기가 아니지진 않음)

 

(2) 비동기 : a + synchronous ⇒ async

- 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식

- setTimeout, addEventListner 등

- 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 모두 비동기적 코드

 

https://velog.io/@mrbartrns/til-16-asynchronous-of-js

- 웹의 복잡도가 올라갈수록 비동기적 코드의 비중이 늘어남

 

> 이 내용이 JS와 관련있어서 중요함 JS가 비동기처리를 할 때가 있기 때문임 전에 정리한 자료 함께 첨부함

https://lin-computer.tistory.com/74

 

[js] callback, promise, async & await

1. Js 특징 : 동기처리(Synchronous) ( vs 비동기(Asynchronous)) 1) 동기처리 와 비동기 처리 Js를 예를 들어 설명해보자. Js는 싱글 스레드 언어로, 코드가 작성된 순서대로 작업을 처리한다. console.log("a") con

lin-computer.tistory.com

 

3) 비동기 작업의 동기적 표현이 필요

(1) 비동기 작업의 동기적 표현 : Promise

- Promise : 비동기 처리에 대해 처리가 끝나면 알려달라는 약속 임

- new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 바로 실행됨. 

그 내부의 resolve(또는 reject) 함수를 호출하는 구문이 있을 경우 resolve(또는 reject) 둘 중 하나가 실행되기 전까지는 다음(then), 오류 (chtch)로 넘어가지 않음

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

(2) 비동기 작업의 동기적 표현 : Promise(2)

- 위 코드의 반복부분을 함수화 한 코드

var addCoffee = function (name) {
	return function (prevName) {
		return new Promise(function (resolve) {
			setTimeout(function () {
				var newName = prevName ? (prevName + ', ' + name) : name;
				console.log(newName);
				resolve(newName);
			}, 500);
		});
	};
};

addCoffee('에스프레소')() //바로 함수가 실행되어야해서 ()를 붙인 것
	.then(addCoffee('아메리카노'))
	.then(addCoffee('카페모카'))
	.then(addCoffee('카페라떼'));

(3) 콜백함수_제너레이터 및 async awit

① 제너레이터

* 가 붙은 함수가 제너레이터 함수. 제너레이터 함수는 실행하면 Iterator 객체가 반환(next()를 가지고 있음)됨.

 

(Iterator 이란? 반복될 수 있는, 반복할 수 있는 이란 뜻을 가졌음. iterator 객체는 next메서드로 순환할 수 있는 객체로서, next 메서드 호출 시, Generator 함수 내부에서 가장 먼저 등장하는 yield에서 stop 이후 다시 next 메서드를 호출하면 멈췄던 부분 -> 그 다음의 yield까지 실행후 stop

즉, 비동기 작업이 완료되는 시점마다 next 메서드를 호출해주면 Generator 함수 내부소스가 위 - 아래 순차적으로 진행됨!!!!!!!!!)

 

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

(4) 비동기 작업의 동기적 표현(4) - Promise + Async/await 

// 드디어!!!!! 드디어 나옴 ㅜㅜㅜㅜ 하..........

var addCoffee = function (name) {
	return new Promise(function (resolve) {
		setTimeout(function(){
			resolve(name);
		}, 500);
	});
};
var coffeeMaker = async function () {
	var coffeeList = '';
	var _addCoffee = async function (name) {
		coffeeList += (coffeeList ? ', ' : '') + await addCoffee(name);
	};
	await _addCoffee('에스프레소');
	console.log(coffeeList);
	await _addCoffee('아메리카노');
	console.log(coffeeList);
	await _addCoffee('카페모카');
	console.log(coffeeList);
	await _addCoffee('카페라떼');
	console.log(coffeeList);
};
coffeeMaker();

//ㅋㅋ..ㅋㅋ.. wetube 챌린지에서 이걸 썼다보니.. 이게 제일 좋아.. 제일 이해가 쉬움.. 역시 해본게 제일 좋다..

 

//TIL 배운대로 하고 싶었지만, 진짜 이론 강의들이라 그냥 들으면서 이해하듯 정리하는 게 최고라 이렇게 해버림.. 🥺🥺담부터는 좀 더 잘해볼게요🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺🥺 오늘은 진짜 컨디션 난조에 공부한 것만 해도 잘했따