본문 바로가기
FRONT-END/JavaScript

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

by 랄라J 2024. 9. 22.

책을 읽은 지는 꽤 지났지만, 다시 한번 복습 겸 기록을 남겨보려고 합니다. 책을 읽고 요약해 정리한 내용의 전문은 제 개인 노션에 정리해 놓았습니다.

 

이전 글 : [FRONT-END/JavaScript] - 03. this (코어 자바스크립트)

 

03. this (코어 자바스크립트)

책을 읽은 지는 꽤 지났지만, 다시 한번 복습 겸 기록을 남겨보려고 합니다. 책을 읽고 요약해 정리한 내용의 전문은 제 개인 노션에 정리해 놓았습니다. 이전 글 : [FRONT-END/JavaScript] - 02. 실행

rarla-j.tistory.com

 

 

콜백 함수

콜백 함수의 정의

단어를 정의해 보면 call(부르다, 호출하다) + back(뒤돌아오다, 되돌다) = 되돌아 호출해 달라는 의미입니다.

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

제어권은 실행되는 시점이나, this 바인딩, 받을 인자와 순서 등에 대한 제어권을 의미합니다.

 

콜백 함수는 함수다

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

obj.logValues(1, 2); // this는 obj
[4, 5, 6].forEach(obj.logValues); // this는 Window

위 코드를 보면 콜백 함수로 어떤 객체의 메서드를 전달하더라도 그 메서드는 메서드가 아닌 함수로 호출됨을 알 수 있습니다.

obj를 this로 하는 메서드를 그대로 전달한 것이 아니라 obj.logValues가 가리키는 함수만 전달해 메서드로 호출할 때가 아닌 한 obj와의 연관이 없어지게 되고 this는 호출한 시점에 전역 객체인 Window가 할당됨을 알 수 있습니다.

 

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

1. 전통적인 방식

self에 this를 담고, 익명 함수를 선언과 동시에 반환하는 방식을 활용했습니다. 

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

var callback = obj1.func()
setTimeout(callback, 1000);

 

2. bind 메서드 활용하기

bind로 this를 직접 지정해주는 방식으로 해결이 가능합니다.

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

var obj2 = { name: 'obj2' }
setTimeout(obj1.func.bind(obj2), 1000);

 

콜백 지옥과 비동기 제어

우선 콜백 지옥이란 콜백 함수를 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상을 의미합니다.

콜백 지옥은 주로 이벤트 처리나 서버 통신과 같이 비동기적 작업을 수행할 때 자주 등장합니다. 가독성이 떨어져 코드를 수정하기 어렵다는 단점이 있습니다.

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);
        }, 1000, '아메리카노');
    }, 1000, '카페라떼');
}, 1000, '콜드브루')

 

동기적인 코드는 현재 실행중인 코드가 완료되면 다음 코드가 실행되어 코드의 흐름이 순차적으로 실행되는 코드를 말합니다.
비동기적인 코드는 현재 실행중인 코드의 완료 여부와 상관없이 즉시 다음 코드가 실행되는 코드를 말합니다.

비동기적인 코드의 예로는 setTimeout(사용자의 요청에 의해 특정 시간이 경과되기 전까지 전달된 콜백 함수의 실행 보류), addEventListener(사용자의 직접적인 개입이 있을 때 전달된 콜백 함수가 실행되도록 대기), XMLHttpRequest(웹브라우저 자체가 아닌 별도의 대상에 뭔가 요청하고 응답이 왔을 때 전달된 콜백 함수가 실행되도록 대기)가 있습니다.

 

콜백 지옥은 주로 비동기적 작업을 수행할 때 자주 등장한다고 했습니다. 현대 자바스크립트는 웹의 복잡도가 증가함에 따라 비동기의 비중이 훨씬 높아졌는데요. 그럼 콜백 지옥을 해결하기 위해서는 어떻게 해야 하는지 알아봅시다!

콜백 지옥 해결 방법

1. 기명 함수로 변환

var coffeeList = '';

var addAmericano = function(name){ 
    coffeeList += ', ' + name;
    console.log(coffeeList);
    setTimeout(addLatte, 1000, '라떼')
}

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

setTimeout(addAmericano, 1000, '아메리카노')

이렇게 넘겨 줄 함수를 변수로 할당해 놓고 실행하도록 하는 방식입니다. 하지만 어떤가요? 일회성 함수를 하나하나 지정하는 것에 불편함을 느낄 수 있고, 코드를 하나하나 따라다녀야 해 특히 코드가 섞여있는 상태면 흐름을 한눈에 파악하기 어렵습니다.

자바스크립트는 이렇게 비동기적인 작업을 동기적인 것처럼 보이게 해주는 장치를 마련하고자 노력해왔습니다.
ES6에서는 Promise, Generator를 도입했고, ES2017에서는 async/await을 도입했습니다.

 

2. Promise

사용 방법은 new 연산자와 함께 Promise를 호출합니다. 인자로 resolve(성공), reject(실패)를 넘겨줄 수 있는데, 내부에 resolve나 reject를 호출한 구문이 있다면, 둘 중 하나가 실행되기 전까지 then 또는 catch 구문으로 넘어가지 않습니다.

즉, 비동기 작업이 완료될 때 비로소 reject, resolve를 호출하는 방법으로 비동기 작업의 동기적인 표현이 가능해집니다.

이전 기명 함수 방법으로 사용했던 코드를 Promise를 활용해 수정해 보면 아래와 같습니다.

new Promise(function(resolve) {
    setTimeout(function() {
        var name = '아메리카노';
        console.log(name);
        resolve(name);
    }, 1000)
}).then(function (prevName) {
    return new Promise(function (resolve) {
        setTimeout(function() {
            var name += prevName + ', 라떼';
            console.log(name);
        }, 1000);
    }
})

중복되는 함수가 불편하다면 아래와 같이 수정할 수 있습니다.

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);
            }, 1000);
        });
    };
};

addCoffee('아메리카노')()
    .then(addCoffee('라떼'))
    .then(addCoffee('콜드브루'))

 

3. Generator

사용방법은 함수에 *을 붙여 Generator 함수임을 표시합니다. Generator 함수를 사용하면 Iterator가 반환되는데, Iterator는 next라는 메서드를 가지고 있습니다. next 메서드를 호출하면 앞서 멈췄던 부분부터 시작해 다음에 등장하는 yield에서 함수의 실행을 멈추게 됩니다. 비동기 작업이 완료되는 시점마다 next 메서드를 호출해 준다면 Generator 함수 내부의 소스가 위에서부터 아래로 순차적으로 진행하게 됩니다.

var addCoffee = function(prevName, name) {
    setTimeout(function() {
        coffeeMaker.next(prevName ? prevName + ', ' + name : name);
    }, 1000);
};

var coffeeGenerator = function* () {
	var americano = yield addCoffee('', '아메리카노');
    console.log(americano);
    var latte = yield addCoffee(americano, '라떼');
    console.log(latte);
}

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

// 아메리카노
// 아메리카노, 라떼

 

4. Promise + async/await

사용방법은 비동기 작업을 수행하고자 하는 함수 앞에 async를 표기하고, 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await을 표기합니다. await을 표기하면 뒤 내용을 자동으로 Promise로 전환하고, 해당 내용이 resolve 된 이후에야 다음 코드로 진행됩니다.

Promise then 메서드와 유사한 효과를 얻을 수 있습니다.

var addCoffee = function(name){
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(name);
        }, 1000);
    });
};

var coffeeMaker = async function() {
    var coffeeList = '';
    var _addCoffee = async function (name) {
    	coffeeList += (coffeeList ? "," : "") + await addCoffee(name);
    }
    await _addCoffee('아메리카노');
    await _addCoffee('라떼');
    await _addCoffee('콜드브루');
    console.log(coffeeList);
}

coffeeMaker();

 

이상으로 콜백 함수의 정의, this 바인딩 방법, 콜백 지옥 해결 방법까지 알아봤습니다!

 

다음 글 :  [FRONT-END/JavaScript] - 05. 클로저 (코어 자바스크립트)

 

05. 클로저 (코어 자바스크립트)

책을 읽은 지는 꽤 지났지만, 다시 한번 복습 겸 기록을 남겨보려고 합니다. 책을 읽고 요약해 정리한 내용의 전문은 제 개인 노션에 정리해 놓았습니다. 이전 글 : [FRONT-END/JavaScript] - 04. 콜백

rarla-j.tistory.com

 

 

728x90

댓글