Skip to content

Latest commit

 

History

History
426 lines (336 loc) · 12.5 KB

콜백함수.md

File metadata and controls

426 lines (336 loc) · 12.5 KB

콜백함수

콜백 함수란?

  • 다른 코드의 인자로 넘겨주는 함수 혹은 회신되는 함수임
  • 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다.

제어권

setInterval

setInterval(callback, milliseconds)

콜백 함수 예제 setInterval )

image

  • setInterval은 ms단위의 주기 단위로 콜백 함수를 실행함

콜백 함수 예제 setInterval )

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

setInterval 구조)

var intervalID = scope.setInterval(func,delay[, param1, param2, ...]);
  • 매개변수에 func는 함수이고, delay는 밀리초(ms) 단위의 숫자이고, 나머지 (param1, param2, …)는 func함수를 실행할 때 매개변수로 전달할 인자이다.
  • func에 넘겨준 함수는 매 delay(ms)마다 실행되며, 그 결과 어떠한 값도 리턴하지 않는다.
  • setInterval를 실행하면 반복적으로 실행되는 내용 자체를 특정할 수 있는 고유한 ID 값이 반환된다. 이를 변수에 담는 이유는 반복 실행되는 중간에 종료(clearInterval)할 수 있게 하기 위해서이다.

forEach

forEach(callbackFn, [thisArg])
  • [callbackFn](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#callbackfn)
    • 배열의 각 요소에 대해 실행할 함수이다. 반환값은 사용되지 않는다.
  • [thisArg](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#thisarg)
    • callbackFn을 실행할 때 this 값으로 사용할 값임

forEach() 예시)

let arr = [1,2,3,4,5];
let entries = [];
arr.forEach(function(v,i){
    entries.push([i, v, this[i]]);
}, [10,20,30,40,50]);
console.log(entries);

결과
[
  [ 0, 1, 10 ],
  [ 1, 2, 20 ],
  [ 2, 3, 30 ],
  [ 3, 4, 40 ],
  [ 4, 5, 50 ]
]

콜백 함수는 함수이다.

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

메서드를 콜백 함수로 전달하는 경우 )

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

// 메서드로서 호출
obj.logValues(1,2) // {vals: [1,2,3], logValues: ƒ} 1 2
[4, 5, 6].forEach(obj.logValues); 
// Window {...} 4 0
// Window {...} 5 1
// Window {...} 6 2
  • 어떤 함수의 인자에 객체의 메서드를 전달하더라도 이는 결국 메서드가 아닌 함수일 뿐이다.

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

콜백 함수 내부에 this에 다른 값을 바인딩하는 방법 )

var obj1 = {
	name:'obj1',
	func: function() {
		var self = this; 
		return function() {
			console.log(self.name);
		};
	}
};
var callback = obj1.func();
setTimeout(callback, 1000); // obj1

콜백 함수 내부에서 this를 사용하지 않은 경우 )

var obj1={
	name: 'obj1',
	func: function(){	
		console.log(obj1.name); 
	}
};
setTimeout(obj1.func,1000); //obj1
  • this를 사용하지 않았을때가 훨씬 간결하고 직관적이다. 하지만 this를 이용해 다양한 상황에 재활용할 수 없게 되었다.

func 함수 재활용 가능 )

...
var obj2= {
	name: 'obj2',
	func: obj1.func
};
var callback2 = obj2.func();
setTimeout(callback2,1500); // obj2

var obj3 = {name: 'obj3'};
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000); // obj3
  • this를 우회적으로 활용함으로써 다양한 상황에서 원하는 객체를 바라보는 콜백 함수를 만들 수 있는 방법이다.

다른 예시)

let arr = [1,2,3,4,5];
let obj = {
    vals: [1,2,3],
    log : function(v,i) {
        if(this.vals) {
            console.log(this.vals, v, i);
        } else {
            console.log(this, v, i);
        }
    }
}
// 메서드로 호출
obj.log(1,2)
//콜백함수로 전달
arr.forEach(obj.log);

결과
[1, 2, 3] 1 2

Window {} 1 0
Window {} 2 1
Window {} 3 2
Window {} 4 3
Window {} 5 4
  • 콜백함수로 값을 전달하면 무조건 함수가 나옴. 그래서 obj로 지정하고 싶으면 bind메서드를 쓰거나, forEach의 두번 째 매개변수로 thisArg에 obj를 넣으면 된다.
arr.forEach(obj.log.bind(obj));
arr.forEach(obj.log, obj)

image

  • 메서드로 호출과 콜백함수로 전달 구분

콜백 지옥과 비동기 제어

  • 콜백 지옥(callback hell) : 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상

비동기와 동기

  • 비동기 : 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어간다.
    • setTimeout, addEventListener, XMLHttpRequest 등
    • 별도의 요청, 실행 대기, 보류 등과 관련된 코드
  • 동기 : 현재 실행 중인 코드가 완료된 후에야 다음 코드를 실행하는 방식
    • CPU의 계산에 의해 즉시 처리가 가능한 대부분의 코드

콜백 지옥 )

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

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

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

// 에스프레소
// 에스프레소,아메리카노
// 에스프레소,아메리카노,카페모카
// 에스프레소,아메리카노,카페모카,카페라떼
  • 0.5초 주기마다 커피 목록을 수집하고 출력한다.
  • 들여쓰기 수준이 과도하게 깊어져 값이 전달되는 순서가 ‘아래에서 위로’ 향하고 있어 어색하다.
  • 이를 해결하는 방법은 익명의 콜백 함수를 모두 기명함수로 전환하는 것이다.

콜백 지옥 해결 - 기명함수로 변환 )

var coffeeList='';

var addEsspresso = 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);;
};

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

// 에스프레소
// 에스프레소,아메리카노
// 에스프레소,아메리카노,카페모카
// 에스프레소,아메리카노,카페모카,카페라떼
  • 이 방식은 코드의 가독성을 높여 위에서 부터 아래로 순서대로 읽혀 내려간다.
  • 자바스크립트에서 비동기적인 작업을 동기적으로, 혹은 동기적인 것처럼 보이게끔 처리해 주는 장치인 ⇒ ES6에서 Promise, Generator ES2017에서 async/await 도입되었다.

비동기를 동기적으로 처리

  • ES6에서 Promise, Generator 도입
  • ES2017에서 async/await 도입

Promise

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

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);
	});
});
  • Promise는 new 연산자와 함께 호출한 Promise의 인자로 넘겨주는 콜백 함수는 호출할 때 바로 실행되지만 내부에 resolve 또는 reject 함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행되기 전까지는 다음(then) 또는 오류 구문(catch)으로 넘어가지 않는다.
  • 따라서 비동기 작업이 완료될 떄 비로소 resolve 또는 reject를 호출하는 방법으로 비동기 작업의 동기적 표현이 가능하다.

비동기 작업의 동기적 표현 - 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('카페라떼'));
  • 위는 이전에 반복적인 내용을 함수화해서 더욱 짧게 표현한 것이다.

Generator

비동기 작업의 동기적 표현 - Generator)

var addCoffee = function(prevName, name) {
	setTimeout(function() {
		coffeeMaker.next(prevName ? prevName + ', ' + name : name);
	}, 500);
};
**①** var coffeeGenerator = function* () { // '*'이 붙은 함수 -> Generator 함수
	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();
  • ①에 ‘*’이 붙은 함수가 바로 Generator함수이다.
  • Generator 함수를 실행하면 Iterator가 반환되고, Iterator는 next라는 메서드를 가진다. 이 next메서드를 호출하면 Generator 함수 내부에서 가장 먼저 등장하는 yield에서 함수의 실행을 멈춘다.
  • 이후 다시 next메서드를 호출하면 앞서 멈췄던 부분부터 시작해서 그 다음에 등장하는 yield에서 함수의 실행을 멈춘다.

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

var coffee = 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();
  • 비동기 작업을 수행하고자 하는 함수 앞에 async를 표기하고, 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await를 표기하는 것만으로 뒤의 내용을 Promise로 자동 전환하고, 해당 내용이 resolve된 이후에야 다음으로 진행한다.
  • 즉, Promise의 then과 흡사한 효과를 얻는다.

정리

  • 콜백 함수는 다른 코드에 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다.
  • 제어권을 넘겨받은 코드는 다음과 같은 제어권을 가진다.
      1. 콜백 함수를 호출하는 시점을 스스로 판단해서 실행함
      1. 콜백 함수를 호출할 때 인자로 넘겨줄 값들 및 그 순서가 정해져 있다. 이 순서를 따르지 않고, 코드를 작성하면 엉뚱한 결과가 나온다.
      1. 콜백 함수의 this가 무엇을 바라보도록 할지가 정해져 있는 경우도 있다. 정하지 않은 경우에는 전역객체를 바로봄. 사용자 임의로 this를 바꾸고 싶을 경우 bind 메서드를 활용하면 된다.
  • 어떤 함수에 인자로 메서드를 전달하더라도 이는 결국 함수로서 실행된다.
  • 비동기 제어를 위해 콜백 함수를 사용하다 보면 콜백 지옥에 빠질 수 있다. Promise, Generator, async/await 등을 사용해서 비동적인 코드를 동기적인 코드로 변환한다.

참고문서

정재남, 코어 자바스크립트