본문 바로가기

Frontend/JavaScript

[JavaScript] 코어 자바스크립트 4장 - 콜백 함수

반응형

아래 책을 읽고 정리한 내용입니다.

http://www.yes24.com/Product/Goods/78586788

 

코어 자바스크립트 - YES24

자바스크립트의 근간을 이루는 핵심 이론들을 정확하게 이해하는 것을 목표로 합니다최근 웹 개발 진영은 빠르게 발전하고 있으며, 그 중심에는 자바스크립트가 있다고 해도 결코 과언이 아니

www.yes24.com

 

 

💡콜백 함수를 통해 자바스크립트의 비동기 처리 발전 과정을 이해할 수 있다.

 

콜백 함수란?

  • 콜백 함수는 다른 코드(함수 또는 메서드)의 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다.
  • 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가진다.
  • 콜백 함수는 함수다. 콜백 함수로 어떤 객체의 메서드를 전달하더라도 그 메서드는 함수로서 호출된다. 별도로 this를 지정하지 않으면 함수 내부에서 this는 전역객체를 바라본다는 뜻이다.

 

bind의 등장

콜백 함수 내부에서 this가 전역객체가 아닌 특정 객체를 바라보게 하고 싶다면 어떻게 해야 할까? 전통적으로는 self 변수에 this를 담고, 이를 클로저로 만들었다. 하지만 이 방식은 불편하고 복잡하다는 문제가 있었고, 이를 해결하기 위해 ES5에서 bind가 등장했다. 

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

setTimeout(obj1.func, 1000); //undefined 출력 (전역객체에 name 프로퍼티가 없으므로)

setTimeout(obj1.func.bind(obj1), 1000); //obj1 출력

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

 

 

콜백 지옥과 비동기 제어

콜백 지옥은 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상을 말한다. 주로 이벤트 처리나 서버 통신과 같이 비동기 작업을 수행하기 위해 이런 형태가 주로 등장하는데, 가독성이 떨어질뿐더러 코드의 유지보수에도 좋지 않다. 이러한 콜백 지옥을 해결하기 위한 방법들을 알아보자.

 

콜백 지옥 예시

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);
    }, 500, '아메리카노')
  }, 500, '카페라떼')
}, 500, '카페모카')

//출력
카페모카
카페모카, 카페라떼
카페모카, 카페라떼, 아메리카노

 

 

기명 함수

익명의 콜백 함수를 기명 함수로 전화하는 것은 가독성 문제를 해결하는 가장 간단한 방법이다. 하지만 위에서부터 아래로 읽는 게 익숙한 우리에게는 여전히 불편하며, 일회성 함수를 전부 변수에 할당하는 것이 좋아 보이지는 않는다.

var coffeeList = ``;

var addAmericano = function(name) {
  coffeeList += `, ${name}`;
  console.log(coffeeList);
}

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

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

setTimeout(addMocha, 500, '카페모카');

 

조금 더 효율적인 방법을 알아보자. 비동기 작업을 동기적인 것처럼 보이게끔 처리해 주는 방법 PromiseGeneratorES6에 도입되었다.

 

Promise

new 연산자로 Promise 객체를 호출하고, 비동기 작업이 완료될 때 resolve 또는  reject를 호출하는 방법으로 비동기 작업의 동기적인 표현이 가능하다. 

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); //resolve가 실행되면 fulfilled 상태가 되어 then으로 넘어감
      }, 500);
    });
  };
};

addCoffee('카페모카')
  .then(addCoffee('카페라떼'))
  .then(addCoffee('아메리카노'));

 

Generator

*를 붙인 Generator함수를 실행하면 next메서드를 가진 Iterator가 반환된다. next 메서드를 호출하면 Generator 내부에서 가장 먼저 등장하는 yield에서 함수 실행을 멈춘다. 비동기 작업이 완료되는 시점마다 next를 호출하면 Generator함수 내부의 코드가 위에서부터 아래로 순차적으로 진행되는 것이다.

var addCoffee = function(prevName, name) {
  setTimeout(function() {
    coffeeMaker.next(prevName ? `${prevName}, ${name}` : name);
  }, 500);
};

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

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

 

위 두 방법은 동기적인 느낌을 주지만 작성법이 조금 어렵다. 더 발전된 방법을 알아보자.

 

async/ await

ES2017에서는 가독성이 뛰어나면서도 작성법도 간단한 새로운 기능 async/await가 추가되었다. 비동기 작업을 수행하고자 하는 함수 앞에 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);
};

coffeeMaker();

 

참고) async를 붙인 함수는 항상 Promise를 반환한다. 

 

 

반응형