코어 자바스크립트 - 04. 콜백함수

iamsummer__·2021년 1월 11일
0

1️⃣ 콜백함수란?

다른 코드의 인자로 넘겨줌으로써 제어권도 함께 위임한 함수

콜백함수를 위임받은 코드는 자체적인 내부 로직에 의해 콜백함수를 적절한 시점에서 실행한다.

📚 map함수를 통해 콜백함수에 대해서 분석해보쟈

[1,2,3].map(function(currentValue, index) {
	console.log(currentValue, index); 
});


Array.prototype.map(callback[, thisArg])
callback: function(currentValue, index, array)

Array의 prototype에 담긴 map 메서드는 위와 같은 구조로 이루어져있다.
map메서드는 첫번째 인자로는 callback 함수를 받고,
생략가능한 두번째 인자로는 콜백함수 내부에서 this로 인식할 대상을 특정할 수 있다.(thisArg 생략할 경우 전역객체가 바인딩된다.)

배열의 모든 요소들을 처음부터 끝까지 하나씩 꺼내어 콜백함수를 반복호출하고, 콜백함수의 실행결과를 모아서 새로운 배열을 만든다.
그리고 콜백함수의 첫번째 인자에는 현재값, 두번째 인자에는 현재값 인덱스, 세번째 인자에는 배열전체가 담긴다.

💻 직접 map함수를 구현해보자

Array.prototype.map = function (callback, thisArg) {
	let mapArr = [];
    for (let i = 0; i < this.length; i ++) {
    	let mapValue = callback.call(thisArg || window, this[i], i, this);
        mapArr[i] = mapValue;
    }
    return mapArr;
}

2️⃣ 콜백함수는 함수이다.

 let obj = {
 	vals: [1,2,3],
    logValues: function(v,i) {
    	console.log(this,v,i);
    }
 }
 
 
 [4,5,6].forEach(obj.logValues);

obj.logValues는 obj객체의 메서드로 정의되어 있다.
이 메서드를 forEach함수의 콜백함수로서 전달했다.
obj를 this로 하는 메서드를 그대로 전달하는 것이 아니라, obj.values가 가리키는 함수만 전달한 것이다.
그러므로 obj와 직접적인 연관이 없어, this는 전역객체를 바라보게 된다.
어떤 함수의 인자에 객체의 메서드를 전달하더라도, 결국 메서드가 아닌 함수일 뿐이다.

📝 만약 콜백함수에 특정 this를 바인딩하고 싶을 때는 bind메서드를 사용하면 된다.

[4,5,6].forEach(obj.logValues.bind(obj)); //obj 객체를 this로 바인딩한 경우

3️⃣ 콜백지옥과 비동기 제어

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

주로 이벤트처리나 서버통신과 같은 비동기 작업수행시에 자주 발생한다.
🚀 예제를 통해서 콜백 지옥부터 어떻게 풀어나가는지 알아보자

💣 콜백지옥

setTimeout(function (val) {
	let text = val;
    setTimeout(function(val) {
    	let text += val;
        setTimeout(function(val) {
    		let text += val;
            console.log(text); // aabbcc
    	}, 10, 'cc' )
    }, 10, 'bb' )
}, 10, 'aa')

setTimeout 콜백안에 setTimeout를 사용하여 콜백이 여러번 충첩된 경우이다.
뎁스가 깊어지니 가독성 최악!! ⚠️⚠️⚠️

💡 콜백지옥 해결 1- 기명함수 변환

let text = '';

const test1 = function(val) {
	text += val;
    setTimeout(test2, 10, 'bb')
    
}

const test2 = function(val) {
	text += val;
    setTimeout(test3, 10, 'cc')
}

const test3 = function(val) {
	text += val;
    console.log(text); //aabbcc
}

test1('aa');

기명함수를 사용하여 가독성 문제는 어느정도 해결하는 것 같지만 역시 일회성 함수를 여러번 사용하다보니 보기 좋지 않다.

💡 콜백지옥 해결 2- Promise

const test = function (name) {
	return function(prev = '') {
    	return new Promise(function(resolve) {
        	setTimeout(function() {
            	let text = prev + name;
                resolve(text);
            }, 10)
        })
    }
} 


test('aa')()
	.then(test('bb'))
    .then(test('cc')); // aabbcc

ES6의 Promise를 이용한 방식이다.
new 연산자와 함께 호출한 Promise의 인자로 넘겨주는 콜백함수는 호출할 때 바로 실행되지만 그 내부에 resolve, reject함수를 호출하는 구문이 있을 경우, 둘 중 하나가 실행되기 전까지는 then또는 오류구문(catch)으로 넘어가지 않는다.

💡 콜백지옥 해결 3- Generator

const test = function (prev, name) {
	setTimeout(function() {
    	testGenerator.next(prev+name);
    }, 10);
}

const generator = function* () {
	let test1 = yield test('', 'aa');
    let test 2 = yield test(test1, 'bb');
    let test3 = yield test(test2, 'cc');
}

const testGenerator = generator();
testGenerator.next();

ES6의 Generator를 사용했다.
* 가 붙은 함수가 generator이다. generator함수를 실행하면 iterator가 반환되는데, iterator는 next라는 메서드를 가지고 있다.
이 next메서드를 호출하면 generator 함수 내부에서 가장 먼저 등장하는 yield에서 함수의 실행을 멈춘다.
후에 다시 next메서드를 호출하면 앞서 멈췄던 부분부터 시작하며, 그 다음 등장하는 yield 에서 함수 실행을 멈춘다.
즉, 비동기 작업이 완료되는 시점마다 next를 호출한다면 generator 함수 내부 소스가 위에서 아래로 순차적으로 실행이 된다.

💡 콜백지옥 해결 4- Promise + Async/Await

const test = function(name) {
	return new Promise(function(resolve) {
    	setTimeout(function() {
        	resolve(name);
        }, 10);
    })
}

const maker = async function() {
	let text = '';
    
   let addText = async function (name) {
   		text += test(name);
   }
   
   await addText('aa')
   await addText('bb')
   await addText('cc')
   
   console.log(text); // aabbcc
}

ES8의 async/await를 사용했다.
비동기 작업을 수행하고자 하는 함수 앞에 async를 표기하고, 함수 내부에 실질적인 비동기 작업이 필요한 위치마다 await를 표기하는 것만으로도 뒤의 내용을 promise로 자동 전환하며,
해당 내용이 resolve 된 이후에야 다음으로 진행한다.
즉, promise의 then과 흡사한 효과를 얻을 수 있다.

📚 정리

📌 콜백함수는 다른 코드에 인자로 넘겨줌으로써 제어권까지 함께 위임한 함수이다.
📌 제어권을 넘겨받은 코드는 콜백함수를 호출하는 시점을 스스로 판단해 실행한다.
📌 제어권을 넘겨받은 코드는 콜백함수를 호출할 때 인자로 넘겨줄 값들의 순서가 정해져있다.
📌 제어권을 넘겨받은 코드는 콜백함수의 this가 무엇을 바라보도록 할지 정할수 있다. (bind메서드)
📌 어떤 함수에 인자로 메서드를 전달하더라도 이는 결국 함수로서 실행된다.
📌 비동기 제어를 위해 콜백함수를 사용하다보면 콜백지옥에 빠진다. 해결책으로는 Promise,Generator, async/await 등이 있다.

profile
개발하는 프론트엔드개발자

0개의 댓글