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

Yongwoo Cho·2022년 6월 19일
0

TIL

목록 보기
95/98
post-thumbnail

4장 - 콜백 함수

✔️ 콜백 함수 : 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수로 콜백 함수를 위임받은 코드는 자체적인 내부 로직에 의해 이 콜백 함수를 적절한 시점에 실행한다.

var newArr = [10, 20, 30].map(function (currentValue, index) {
  console.log(currentValue, index);
  return currentValue + 5;
});

console.log(newArr);

map 메서드는 메서드의 대상이 되는 배열의 모든 요소들을 처음부터 끝까지 하나씩 꺼내어 콜백 함수를 반복 호출하고, 콜백 함수의 실행 결과들을 만든다.

✔️ 콜백 함수의 this

// Array.prototype.map 직접 구현
Array.prototype.map = function (callback, thisArg) {
  var mappedArr = [];
  for (var i = 0; i < this.length; i++) {
    var mappedValue = callback.call(thisArg || window, this[i], i, this);
    mappedArr[i] = mappedValue;
  }
  return mappedArr;
};

this에는 thisArg 값이 있을 경우에는 그 값을, 없을 경우에는 전역객체를 지정한다. 제어권을 넘겨받을 코드에서 call/apply 메서드의 첫 번째 인자에 콜백 함수 내부에서의 this가 될 대상을 명시적으로 바인딩 하기 때문에 this에 다른 값이 담기게 된다.

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

1 : obj 객체의 logValues는 메서드로 정의됐다. obj.logValues로ㅓ 메서드의 이름 앞에 점이 있으니 메서드로서 호출한 것이다. 따라서 this는 obj를 가리킨다.

2 : forEach 함수의 콜백 함수로서 메서드를 전달했다. obj를 this로 하는 메서드를 그대로 전달한 것이 아니라, obj.logValues가 가리키는 함수만 전달한 것이다. 따라서 함수 내부에서의 this는 전역객체를 바라보게 된다.

❓ 콜백 함수 내부에서 this가 객체를 바라보게 하고 싶으면?

👉 ES5 이전 : this를 다른 변수에 담아 콜백 함수로 활용할 함수에서는 this 대신 그 변수를 사용하게 하고, 이를 클로저로 만든다.

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

👉 ES5 이후 : bind 메서드 이용

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

✔️ 동기 vs 비동기

  • 동기 : 현재 실행 중인 코드가 완료된 후에야 다음 코드를 실행하는 방식
  • 비동기 : 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식

사용자의 요청에 의해 특정 시간이 경과되기 전까지 어떤 함수의 실행을 보류한다거나(setTimeout), 사용자의 직접적인 개입이 있을 때 비로소 어떤 함수를 실행하도록 대기한다거나(addEventListener), 웹브라우저 자체가 아닌 별도의 대상에 무언가를 요청하고 그에 대한 응답이 왔을 때 비로소 어떤 함수를 실행하도록 대기하는 등(XMLHttpRequest), 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 비동기적인 코드다.

✔️ 콜백지옥

콜백지옥은 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상으로 가독성이 떨어질뿐더러 코드를 수정하기도 어렵다.

개선방법

  • Promise

    new 연산자와 함께 호출한 Promise의 인자로 넘겨주는 콜백 함수는 호출할 때 바로 실행되지만 그 내부에 resolve 또는 reject 함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행되기 전까지는 다음(then) 또는 오류구문(catch)로 넘어가지 않는다. 따라서 비동기 작업이 완료될 때 비로소 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);
          }, 500);
        });
      };
    };
    addCoffee("에스프레소")()
      .then(addCoffee("아메리카노"))
      .then(addCoffee("카페모카"));
  • Generator

    Generator 함수를 실행하면 Iterator가 반환되는데, Iterator는 next라는 메서드를 갖고 있다. 이 next 메서드를 호출하면 Generator 함수 내부에서 가장 먼저 등장하는 yield에서 함수의 실행을 멈춘다. 이후 다시 next 메서드를 호출하면 앞서 멈췄던 부분부터 시작해서 그 다음에 등장하는 yield에서 함수의 실행을 멈춘다. 따라서 비동기 작업이 완료되는 시점마다 next 메서드를 호출해준다면 Generator 함수 내부의 소스의 동기적 표현이 가능하다.

    var addCoffee = function (prevName, name) {
      setTimeout(function () {
        coffeeMaker.next(prevName ? prevName + ", " + name : name);
      }, 500);
    };
    
    var coffeeGenerator = function* () {
      var espresso = yield addCoffee("", "에스프레소");
      var americano = yield addCoffee(espresso, "아메리카노");
      var mocha = yield addCoffee(americano, "카페모카");
    };
    var coffeeMaker = coffeeGenerator();
    coffeeMaker.next();
  • Promise + async / await

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

    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("에스프레소");
      await _addCoffee("아메리카노");
      await _addCoffee("카페모카");
    };
    coffeeMaker();
profile
Frontend 개발자입니다 😎

0개의 댓글