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

변진상·2024년 5월 14일
0

학습 기록

목록 보기
24/31

코어 자바스크립트를 읽고 스터디 세션에서 공유를 위해 정리한 글입니다.

1. 콜백 함수란?

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

→ 제어권(함수의 실행 등…)과 관련이 깊다.

2. 제어권

4-2-1. 호출 시점

콜백함수의 경우 제어권을 가진 다른 코드에 호출 시점에 대한 제어권을 넘긴다.

// 디바운싱
var timer;
var cbFunc = function() {
    // do something...
}

document.querySelector('#input').addEventListener('input', function(e) {
  if (timer) {
    clearTimeout(timer);
  }
  timer = setTimeout(cbFunc, 200);
});

// 스로틀링
var timer;
document.querySelector('#input').addEventListener('input', function(e) {
  if (!timer) {
    timer = setTimeout(cbFunc, 200);
  }
});
  • [Window. or Worker.]setInterval에 전달한 첫번째 인자 → 콜백함수, 이 함수의 제어권 → setInterval이 가짐
code호출 주체제어권
cbFunc();사용자사용자
setInterval(cbFunc, 200);setIntervalsetInterval
  • 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가진다.

4-2-2. 인자

콜백함수의 경우 제어권을 가진 다른 코드에 인자의 규칙에 대한 제어권을 넘긴다.

var newArr = [1, 2, 3].map(function (currentValue, index) {
	return currentValue + 5;
})

console.log(newArr);
// [6, 7, 8]
Array.prototype.map(callback[, thisArg])
callback: function(currentValue, index, array)
  • 만약 map함수에서 제어권을 가지고있는 인자의 규칙을 어길 경우 예상하는 동작을 하지 않는다.
  • 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길 것인지 → 전적으로 호출 주체에 달려있다.

4-2-3. this

  • 콜백함수도 함수 → 기본적으로 this는 전역객체
  • 제어권을 넘겨받을 코드에서 별도로 this 지정 → 지정한 대상 참조

map 메서드의 경우

Array.prototype.map = function(callback, thisArg){
  var mappedArr = [];
  for (var i = 0; i < mappedArr.length; i++){
    var mappedValue = callback.call(thisArg || Window, this[i], i, this);
    mappedArr.push(mappedValue);
  }
  return mappedArr;
}
  • map에서 콜백함수의 this를 지정하고 있다.

addEventListener 메서드의 경우

document.body.querySelector('#a').addEventListener('click', function (e) {
  console.log(this); // <button id="a">클릭</button>
}) 

3. 콜백 함수는 함수다.

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

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

obj.logValues(1, 2); // obj { ... }, 1, 2

[4, 5, 6].forEach(obj.logValues) // Window { ... }, x, x ...

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

전통적인 방법 → this를 다른 변수에 담아 이를 클로저로 이용하는 방식

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

var callback = obj1.func();
setTimeout(callback, 300); // obj1
  • 이점: obj1의 func를 재사용 할 수 있다.
var obj1 = {
  name: "obj1",
  func: function () {
    var self = this;
    return function () {
      console.log(self.name);
    };
  },
};

var obj2 = {
  name: "obj2",
  func: obj1.func,
};

var callback1 = obj1.func();
setTimeout(callback1, 300); // obj1

var callback2 = obj2.func();
setTimeout(callback2, 300); // obj1

bind 메서드 활용

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

var obj2 = {
  name: "obj2",
};

setTimeout(obj1.func.bind(obj1), 300); // ojb1

setTimeout(obj1.func.bind(obj2), 300); // ojb2

5. 콜백 지옥과 비동기 제어

  • 콜백 지옥: 콜백 함수를 익명 함술로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 어려울 정도로 깊어지는 현상.
  • 동기적 코드: CPU의 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적인 코드이다. 계산식이 복잡해서 CPU가 계산하는 데 시간이 많이 필요한 경우라 하더라도 동기적 코드이다.
  • 비동기적 코드: 별도의 요청, 실행대기, 보류와 관련된 코드이다.
    • 사용자의 요청에 의한 특정 시간 경과되기 전까지 함수의 실행을 보류(SetTimeout), 사용자의 개입이 있을 때 까지 실행 보류(addEventListener), 별도의 대상에 요청과 응답까지 대기(XMLHttpRequest) 등…

콜백 지옥 해결

기명함수로 변환

var coffeeList = '';

var addEspresso = 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(addEspresso, 500, '에스프레소')

Promise 사용

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 사용

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

var coffeeGenerator = function* () {
  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();
  • ES6 Generator → 7번 줄에 *이 붙은 함수가 Generator 함수이다.
    • Generator 함수 실행시 Iterator가 반환되는데 next라는 메서드를 가지고 있다. next 메서드를 실행하면 가정 먼저 등장하는 yield에서 함수의 실행을 멈춘다. 이후 다시 next를 실행하면 다음 yield에서 함수 실행을 멈춘다.
    • 비동기 작업이 완료되는 시점마다 next를 실행하면 내부의 소스가 순차적으로 진행된다.

Promise + 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);
  await _addCoffee("카페라떼");
  console.log(coffeeList);
}

coffeeMaker();

6. 정리

  • 콜백 함수는 다른 코드에 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다.
  • 제어권을 넘겨받은 코드는 다음과 같은 제어권을 가진다.
    • 호출 시점
    • 인자로 넘겨줄 값들 및 그 순서에 관한 규칙
    • 콜백 함수의 this를 정하는 경우도 있다. 정하지 않은 경우 전역객체를 가리킨다. 사용자 임의로 this를 바꾸고자 한다면 bind 메서드를 사용하면 된다.
  • 어떤 함수에 인자로 메서드를 전달하더라도 이는 결국 함수로서 실행된다.
  • 콜백 지옥의 대안: 기명함수 사용, Promise, Generator, async/await 등
profile
자신을 개발하는 개발자!

0개의 댓글