본문 바로가기
프로그래밍/JavaScript

그래서 콜백이 뭔데? 자바스크립트 CALL BACK

by 물고기고기 2021. 8. 8.

비동기 처리? 자바스크립트의 비동기 처리란 특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 자바스크립트의 특성을 의미합니다.

 

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

 

2. 제어권

2-1. 호출 시점

var count =0;
var timer = setInterval(function(){
    console.log(count);
		// clearInterval은 즉시 종료하란 의미
    if(++count>4) clearInterval(timer);
},300)

// setInterval 구조
// func에는 실행 로직 , delay는 시간의 값
var intervalId = scope.setInterval(func,delay[params]);

이를 호출주체와 제어권을 변경한다면

var count = 0;
var cbFunc = function(){
    console.log(count);
    if(++count>4) clearInterval(timer);
};
var timer = setInterval(cbFunc,300);

여기서 cbFunc가 콜백함수.

cbFunc:

  • 호출주체: 사용자
  • 제어권: 사용자

setInterval:

  • 호출주체: setInterval
  • 제어권: setInterval

결론은 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가짐.

2-2. 인자

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

console.log(newArr);
// 실행결과
// 10 0 , 20 1 , 30 2 , [15,25,35]

map 메서드는 이런 구조

Array.prototype.map(callback,[thisArg])
callback: function(currentValue,index,array)
  1. map 메서드는 첫번째 인자로 callback, 두번째 인자로 this로 인식할 대상을 특정. 이를 생략할 경우엔 전역객체가 바인딩
  1. 콜백 함수의 첫번째 인자에는 배열 요소 중 현재값, 두번째는 현재값의 인덱스, 세번째는 map 메서드의 대상이 되는 배열 자체

여기서 알고가야할 점은 map의 메소드는 저 위의 인자 순서를 지켜서 출력값을 기대해야 함. 그러니 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서롤 넘길 것인지에 대한 제어권을 가지고 있다는 뜻.

 

2-3. this

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

 

Array.prototype.map 구현

call/apply 메서드

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
};

위의 코드는 call/apply 메서드의 첫번째 인자에 콜백 함수 내부에서의 this가 될 대상을 명시적으로 바인딩한 것

첫번째 인자에는 this가 배열을 가리킬 것이므로 배열의 i번째 요소의 값을, 두번째 인자에는 i값을 세번째 인자에는 배열 자체를 지정해 호출.

 

3. 콜백 함수는 함수일까?

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

// 메서드를 콜백 함수로 전달한 경우

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

obj.logValues(1,2); // { vals: [ 1, 2, 3 ], logValues: [Function: logValues] } 1 2
[4,5,6].forEach(obj.logValues); // 전역객체

obj객체의 logValues는 메서드로 정의됨. 그렇기에 호출했을때 this는 obj의 객체를 뱉음.

그러나 다음코드는 메서드를 forEach함수의 콜백함수로서 전달함(obj를 this로하는 메서드를 전달한게 아니라 obj.logValues가 가리키는 함수만 전달한 것) 이때는 별도로 this를 지정하는 인자를 지정하지 않았으므로 this가 전역객체를 바라보게 됨

 

Ps. 원래 객체의 메서드를 호출하면 this의 값이 객체였음을 기억해야함

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

  1. 콜백 함수 내부의 this에 다른 값을 바인딩 하는 방법
// 콜백 함수 내부의 this에 다른값을 바인딩하는 전통적인 방식

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

var callback = obj1.func(); // 메서드 호출
setTimeout(callback,1000); // obj1

// 물론 이렇게 호출해도됩니다
setTimeout(obj1.func(),1000); // obj1

 

// 함수를 재활용하지 않고 this를 선언하는 방법
var obj1 = {
    name: 'obj1',
    func: function(){
        console.log(obj1.name);
    }
};

setTimeout(obj1.func,1000);

 

// 재활용하면서 this를 원하는 값으로 선언
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,150); // obj2

// call은 this를 직접 원하는 값으로 호출가능
var obj3 = {name:'obj3'};
var callback3 = obj1.func.call(obj3); // obj3
setTimeout(callback3,200);
  1. 전통적인 방식도 아니고 객체를 명시적으로 지정하지 않고도 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),1500);

 

5. 대망의 콜백지옥

콜백이란? 결국 비동기 처리를 위함.

동기적인 코드는 현재 실행 중인 코드가 완료된 후에야 다음 코드를 실행함.

그러나 자바스크립트는 싱글 스레드이기때문에 오래걸리는 작업들을 나중에 수행하는 방식을 채택하기에 동기적으로 처리되지 않음.

 

비동기적으로 처리되는 코드?

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

 

  • 비동기처리

    비동기처리

    콜백함수의필요성

    loadScript('/my/script.js'); // script.js엔 "function newFunction() {…}"이 있습니다. newFunction(); // 함수가 존재하지 않는다는 에러가 발생합니다!

    예를 들어 시간이 엄청 걸리는 처리를 자바스크립트한테 시켰는데 특정 코드를 실행시키고 그 다음걸 실행시켜야하는것임. 원래 자바스크립트한테 이걸 콜백처리를 안한채 그대로 시키면 시간이 긴 처리를 뒤로 보내버려서 그 다음코드를 실행시킨다면 에러가 나는데 이를 제어하는 것이 콜백함수인거임 loadScript > newFunction(loadScript안에있는) 이런게 콜백.

    이런식으로 콜백선언을 하면 해결할수있음

    loadScript('/my/script.js', function() { // 콜백 함수는 스크립트 로드가 끝나면 실행됩니다. newFunction(); // 이제 함수 호출이 제대로 동작합니다. ... });

    그런데 이런건 콜백지옥을 낳을 수 있음 (이거 실행하고(이거실행하고(또이거실행해))) < 이렇게

비동기 처리가 발전한 방향성

콜백 > 프로미스 > async/await

이전에 콜백함수의 로직이 [[[[]]]]같은방식이었다면 프로미스는 [> > >]같은느낌

 

콜백지옥 예시

setTimeout(
  (name) => {
    let coffeeList = name;
    console.log(coffeeList);

    setTimeout(
      (name) => {
        coffeeList += ', ' + name;
        console.log(coffeeList);

        setTimeout(
          (name) => {
            coffeeList += ', ' + name;
            console.log(coffeeList);

            setTimeout(
              (name) => {
                coffeeList += ', ' + name;
                console.log(coffeeList);
              },
              500,
              'Latte',
            );
          },
          500,
          'Mocha',
        );
      },
      500,
      'Americano',
    );
  },
  500,
  'Espresso',
);

 

이걸 해결하는 간단한 방법 : 기명함수로 전환

let coffeeList = '';

const addEspresso = (name) => {
  coffeeList = name;
  console.log(coffeeList);
  setTimeout(addAmericano, 500, 'Americano');
};

const addAmericano = (name) => {
  coffeeList += ', ' + name;
  console.log(coffeeList);
  setTimeout(addMocha, 500, 'Mocha');
};

const addMocha = (name) => {
  coffeeList += ', ' + name;
  console.log(coffeeList);
  setTimeout(addLatte, 500, 'Latte');
};

const addLatte = (name) => {
  coffeeList += ', ' + name;
  console.log(coffeeList);
};

setTimeout(addEspresso, 500, 'Espresso');

 

Promise로 콜백지옥 해결하기

new Promise((resolve) => {
  setTimeout(() => {
    let name = 'Espresso';
    console.log(name);
    resolve(name);
  }, 500);
})
  .then((prevName) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        let name = prevName + ', Americano';
        console.log(name);
        resolve(name);
      }, 500);
    });
  })
  .then((prevName) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        let name = prevName + ', Mocha';
        console.log(name);
        resolve(name);
      }, 500);
    });
  })
  .then((prevName) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        let name = prevName + ', Latte';
        console.log(name);
        resolve(name);
      }, 500);
    });
  });

Promise의 가독성을 높인다면

const addCoffee = (name) => {
  return (prevName) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        const newName = prevName ? `${prevName}, ${name}` : name;
        console.log(newName);
        resolve(newName);
      }, 500);
    });
  };
};

addCoffee('Espresso')()
  .then(addCoffee('Americano'))
  .then(addCoffee('Mocha'))
  .then(addCoffee('Latte'));

 

그리고 대망의 async/await + Promise

const addCoffee = (name) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(name);
        }, 500);
    });
};

const coffeeMaker = async () => {
    let coffeeList = '';
    let _addCoffee = async (name) => {
        coffeeList += (coffeeList ? ', ' : '') + (await addCoffee(name));
    };
    await _addCoffee('Espresso');
    console.log(coffeeList);
    await _addCoffee('Americano');
    console.log(coffeeList);
    await _addCoffee('Mocha');
    console.log(coffeeList);
    await _addCoffee('Latte');
    console.log(coffeeList);
};

coffeeMaker();

비동기 작업을 수행하고자 하는 함수 앞에 async를 표기하고, 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await를 표기.

이 경우엔 promise함수가 resolve 된 후에야 다음으로 진행하는 셈

 

콜백 함수 요약

  1. 콜백 함수란? 다른 코드에 인자를 넘겨줘서 제어권도 함께 위임한 함수
  1. 제어권을 넘겨받은 코드는 다음과 같은 제어권을 가짐
    • 콜백 함수를 호출하는 시점을 스스로 판단해서 실행
    • 콜백 함수를 호출할 때 인자로 넘겨줄 값들 및 그 순서가 정해져 있음.
    • 콜백 함수의 this가 무엇인지 정할 수 있으나 정하지 않는다면 전역객체를 바라보게 됨. (함수이기때문)
    • 어떤 함수에 인자로 메서드를 전달하더라도 이는 함수로서 실행
    • 비동기 제어를 위해 콜백 지옥에 빠지지 않게 나온것이 Promise, async/await

 

댓글