CallBack함수가 뭐에요?

HeeSeok·2023년 7월 27일
0

CallBack 함수란?

함수를 호출할때 함수 인자로 다른 함수를 넘겨주어 내부에서 콜백함수를 실행시키는 방식 입니다.

ex) 예제 코드

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

제어권

var count = 0;
var clear = function () {
  if(++count > 4) clearInterval(timer);
}
var timer = setInterval(clear, 300)

setInterval함수를 호출할때 clear는 setInterval 내부에서 cbFunc는 함수는 0.3초마다 반복해서 수행이 된다.

이를 통해 clear함수의 제어권과 호출은 setInterval에 있음을 알 수 있다.

인자

예제2)map

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

위의 코드에서 map 함수의 콜백함수를 호출하기 위해서는 map메서드에서 정의된 인자와 순서가 있다.

제어권을 넘겨받은 코드(map)는 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길 것인지에 대한 제어권을 가진다.

this

setTimeout(function() {console.log(this);}, 300) //window

[1,2,3,4,5].forEach(function(x) {
  console.log(this)								//window
})

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

콜백함수도 함수이기 때문에 this는 기본적으로 전역객체를 참조하지만 제어권을 넘겨받을 코드에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조한다.

콜백 함수는 함수다

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

콜백 함수도 함수이기 때문에 어떤 객체의 메서드를 전달하더라도 그 메서드는 메서드가 아닌 ‘함수’로서 호출된다.

1) obj 객체의 logValues는 메서드로 정의된다. 따라서 this는 object를 가르킨다.
2) obj 객체의 logValues는 메서드는 forEach에 의해 콜백이 함수로서 호출이 된다. 이때 별도로 this를 지정하지 않았으므로 함수 내부에서의 this는 전역객체를 바라본다.

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

객체의 메서드를 콜백 함수로 전달한 경우 메서드는 해당 객체를 this로 바라볼 수 없게 된다.
객체의 메서드가 객체를 바라보게하고 싶다면 다음과 같은 방법을 사용한다.

  1. 클로저 사용
var obj1 = {
  name: 'obj1',
  func: function() {
    var	self = this;
    return function() {
      console.log(self.name)
    };
  }
} 
var callback = obj1.func();
setTimeout(callback,1000)

func메서드 내부에서 self에 this를 담고 클로저를 사용해서 this를 구현한 예제입니다.(현재는 비추천하는 방식)

  1. 객체자체를 호출
var obj1 = {
  name: 'obj1',
  func: function() {
    console.log(obj1.name)
  }
}
setTimeout(obj1.func, 1000)

func 메서드에서 객체를 직접 명시해서 호출하는 방식이다. 간결하고 직관적 이지만 재사용성이 떨어지게 된다.

  1. 객체복사 및 call메서드 사용
var obj1 = {
  name: 'obj1',
  func: function() {
    var	self = this;
    return function() {
      console.log(self.name)
    };
  }
} 
var obj2 = {
  name: 'obj2',
  func: obj1.func
}
var callback2 = obj2.func();
setTimeout(callback2,1000);

var obj3 = {name: 'obj3'};
var callback3 = obj1.func.call(obj3);
setTimeout(callback3,1000);

해당 방법은 obj1의 func함수를 재사용 할때 해당 함수가 재사용할 객체를 바라보게 하는 방법이다. obj2의 경우는 obj1에서 self를 통해 this를 설정해 obj1의 name이 출력된다.

이를 해결하기 위해 call를 사용해 this를 obj3을 가르키도록 우회하였다.

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

바인드 함수를 사용해 this를 지정해주는 방법이다. 위의 클로저보다 훨씬 코드의 가독성이 올라갔다.

콜백지옥

콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상으로, 자바스크립트에서 흔히 발생하는 문제이다.

주로 이벤트 처리나 서버 통신과 같이 비동기적인 작업을 수쟁하기 위해 이런 형태가 자주 등장한다.

콜백지옥의 예시

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)
            setTimeout(
              function (name) {
                coffeeList += ', ' + name
                console.log(coffeeList)
              },
              500,
              '에스프레소',
            )
          },
          500,
          '카페라떼',
        )
      },
      500,
      '카페모카',
    )
  },
  500,
  '아메리카노',
)

해결방법

  1. 기명함수로 전환
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(addLatte, 500, "카페라떼");
};

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

setTimeout(addEspresso, 500, "에스프레소");

이 방식의 코드는 함수 선언과 함수 호출만 구분할 수 있다면 위에서부터 아래로 순서대로 읽어내려갈 수 있어 가독성이 높다. 또한 변수를 최상단으로 끌어올림으로써 외부에 노출되는 문제가 있지만 즉시 실행 함수 등으로 감싸면 간단히 해결된다.

하지만 코드명을 일일이 따라다녀야 하고 일회성 함수를 전부 변수에 할당하는 것은 효율적이지는 않은 코드다.

  1. 비동기 작업의 동기적 표현 - 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('카페라떼'));

ES6의 Promise를 이용한 방식으로 new 연산자와 함께 호출한 Promise는 호출할 때 바로 실행되지만 resolve 또는 reject 함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행 되지 전까지는 다음(then) 또는 오류 구문(catch)로 넘어가지 않는다. 따라서 비동기 작업이 완료되어야만 resolve 또는 reject를 호출하는 방법으로 비동기 작업의 동기적 표현이 가능하다.

  1. 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를 사용한 방법으로 *이 붙은 함수가 Generator 함수이다.
Generator를 실행하면 Iterator가 반환되고 Iterator는 next라는 메서드를 가지고 있다.

next 메서드를 실행하면 yield에서 실행을 멈추고 다시 next를 호출하면 그 다음 yield를 만나기 전까지 코드가 수행된다.

  1. 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()

ES2017에서 가독성도 뛰어나고, 작성법도 간단한 async/await 기능이 추가되었다.
비동기 작업을 수행하고자 하는 함수 앞에 async를 표기하고 비동기 작업이 필요한 위치마다 await를 표기하면 뒤의 내용을 Promise로 자동 전환하고, 해당 내용이 resolve 된 이후에야 다음으로 진행하게 된다.

0개의 댓글