Jaeilit

코어 - 콜백함수 본문

JavaScript

코어 - 콜백함수

Jaeilit 2022. 6. 20. 17:21
728x90

콜백함수는 다른 코드의 인자로 넘겨주는 함수

콜백함수의 활용 -> 동기적인 작업이 가능한 콜백함수는 비동기 함수 호출 후 콜백함수로 동기적인 작업을 할 수 있다.

let count = 0;

let timer = setInterval(function () {
  console.log(count); // 0, 1, 2, 3

  if (++count > 4) clearInterval(timer);
}, 300);

let countb = 0;

let cbfunc = function () {
  console.log(countb, "b");
  //0, 1, 2, 3

  if (++countb > 4) {
    clearInterval(timerb);
  }
};

let timerb = setInterval(cbfunc, 300);

setInterval 의 파라미터 값으로 cbfunc을 받아서 cbfunc의 제어권을 받은 setInterval이 cbfunc의 함수를 실행함

콜백함수도 함수다.

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
[4, 5, 6].forEach(obj.logValues); // Window { ... } 4 0 , 5 1, 6 2

logValues 를 메서드 호출 했을 경우에는 올바르게 this는 vals, logVlues를 가르키지만

forEach 의 콜백으로 obj.logValues 를 호출 했지만 저번시간에 배운 듯이 일반함수의 This 로 동작하면서 window를 가르키고 있다.

 

this 를 바인딩 하려면?

this를 다른 변수에 저장하여 쓰거나, 명시적 바인딩으로 배운 apply, call, bind 를 사용하면 된다.

 

this 다른 변수에 저장하기)

 

바로 위의 코드를 this 를 다른 변수에 저장하여 호출 한 모습

콜백 함수의 this 는 obj 환경을 가르키고 있다.

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

실제로 이렇게 쓰이지 않고 번거롭다고 한다..

 

this 를 쓰지 않는 방법도 있다.

this 를 쓰지 않으면 확실히 간결하고 직관적이겠지만 this를 이용해 다양한 상황에서 재활용 할 수 없게 된다.

 

func 함수 재활용

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

var callback = obj1.func();
setTimeout(callback, 1000);

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

var callback2 = obj2.func();
setTimeout(callback2, 1500);

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


// obj1,
// obj2,
// obj3

설명)

  • 1초 후 obj, 1.5초 후 obj2, 2초 후 obj3 이출력
  • obj1.func 호출 시 this는 obj1
  • obj2.func 으로 obj1.func 을 호출 했지만 this는 obj2의 환경을 가진다.
  • obj1.func 에 obj3을 This로 바인딩함

코드 수정) this.name

var obj1 = {
  name: "obj1",
  func: function () {
    console.log(this.name);
  },
};
setTimeout(obj1.func.bind(obj1), 1000);
// obj1.func 에 obj1 객체 this 를 바인딩함
// obj1

var obj2 = { name: "obj2" };
setTimeout(obj1.func.bind(obj2), 1500);
// obj1.func 에 obj2 객체 this 를 바인딩함
// obj2

콜백지옥

콜백지옥은 콜백 함수에 콜백 함수를 그 콜백함수의 인자로 또 콜백함수를... 이러한 형태를 말한다.

 

콜백함수를 사용하는 이유 중에 하나는 비동기 작업을 위해서인데, 콜백에 콜백을 콜백에 콜백을 사용하면 가독성도 떨어지고 코드 수정도 어렵다.

 

동기는 현재 실행중인 코드가 완료 후에야 다음 코드를 실행하는 방식

비동기는 현재 코드 완료 여부와 상관 없이 다음 코드로 넘어감,

 

브라우저 실행 할때 서버에서 받아온 데이터를 다 받은 후에 렌더링을 한다면 아마 엄청 느리다고 생각을 할 수 도 있다.

이런 서버 통신이나 이벤트 처리를 비동기작업으로 한다.

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,
  '에스프레소',
);

//'에스프레소'
// '에스프레소, 아메리카노'
// '에스프레소, 아메리카노, 카페모카'
// '에스프레소, 아메리카노, 카페모카, 카페라떼'

원하는 값을 얻더라도 코드의 유지보수도 힘들뿐더러 가독성도 안좋다.

이러한 개선점으로 ES6 에는 promise, generator 등이 도입되었고, ES2017 에서는 async/await이 도입 됨,

 

1. Promise

new Promise(function (resolve) {
  setTimeout(function () {
    var name = '에스프레소';
    console.log(name);
    resolve(name);
  }, 500);
})
  .then(function (prevName) {
    return new Promise(function (resolve) {
      setTimeout(function () {
        var name = prevName + ', 아메리카노';
        console.log(name);
        resolve(name);
      }, 500);
    });
  })
  .then(function (prevName) {
    return new Promise(function (resolve) {
      setTimeout(function () {
        var name = prevName + ', 카페모카';
        console.log(name);
        resolve(name);
      }, 500);
    });
  })
  .then(function (prevName) {
    return new Promise(function (resolve) {
      setTimeout(function () {
        var name = prevName + ', 카페라떼';
        console.log(name);
        resolve(name);
      }, 500);
    });
  });

//Promise { <pending> }


// '에스프레소'
// '에스프레소, 아메리카노'
// '에스프레소, 아메리카노, 카페모카'
// '에스프레소, 아메리카노, 카페모카, 카페라떼'

Promise 는 3가지의 상태 값을 가진다.

  • 대기(pending): 이행하지도, 거부하지도 않은 초기 상태.
  • 이행(fulfilled): 연산이 성공적으로 완료됨.
  • 거부(rejected): 연산이 실패함.

여기서 처음 호출 당시에 pending(대기) 으로 비동기코드를 처리하고 성공하게되면 fuillfiled(풀필) 상태가 되서 resolve 를 반환하고,

rejected 일 경우 reject를 반환한다.

 

resolve 상태가 되면 성공한 값을 then의 콜백으로 then((respone) =>  respone) 로 받아 볼 수 있다.

reject 상태에서도 then 은 사용가능하나, catch 를 이용하여 실패의 경우 로직을 수행한다.

then 과 catch 이외에도 finally 메서드가 있는데 성공, 실패 여부와 관계없이 무조건 실행하는 코드이다.

 

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();

Generator 함수는 redux-saga 에서도 쓰이는데 function 옆에 *를 붙이는게 특징이다.

함수 내 로직을 수행하다가 yield 를 만나면 함수 실행을 멈추게 되고

next() 를 호출하면 그 다음 yield 까지 실행하는 구조이다.

 

비동기 작업이 완료되는 시점마다 next 메서드를 호출해주면 generator 함수 내부의 소스가 위에서부터 아래로 순차적으로 진행 될 것이다.

 

async await

함수 앞에 async 를 표기하고 비동기 작업이 필요한 위치마다 await 을 표기함으로 Promise 를 반환하고 then.catch 문을 사용할수있다.

 

728x90

'JavaScript' 카테고리의 다른 글

코어 - 프로토타입  (0) 2022.07.12
코어 - 클로저  (0) 2022.06.30
코어 this(2)  (0) 2022.06.08
코어 this  (0) 2022.06.07
코어 스코프, 스코프 체인, 외부환경참고  (0) 2022.06.02