콜백 함수란?

  • 다른 코드의 인자로 넘겨주는 함수
  • call 부르다,호출하다 + back 뒤돌아오다, 되돌다 의 합성어
  • → 되돌아 호출해달라.
  • 콜백 함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임

제어권

호출시점

var intervalId = scope.setInterval(func,delay[, param1,param2,...]);
  • setInterval 함수는 funcdelay를 필수 인자로 받으며 param은 선택적으로 func에서 사용할 매개변수를 전달
var count = 0;
var cbFunc = function () {
    console. log(count);
    if (++count > 4) clearInterval (timer);
}

var timer = setInterval(cbFunc,300);
  • 다음 함수는 cbFunc이라는 함수를 setInterval이라는 다른 코드(함수)의 인자로 넘겨준다 → 콜백함수
  • 이로 인해서 cbFunc를 실행하는 제어권을 setInterval이 가지게 된다
code 호출 주체 제어권
cbFunc(); 사용자 사용자
setInterval(cbFunc,300); setInterval setInterval

콜백함수의 인자

Array.prototype.map(callback[,thisArg])
callback: function(currentValue,index,array)
  • map 매서드는 첫 번째 인자callback 함수를 받고, 생략 가능한 두 번째 인자로 콜백함수 내부에서 this로 인식할 대상을 특정할 수 있다.
  • 두 번째 인자가 생략되면 전역객체가 바인딩.
var newArr2 = [10,20,30].map(function (index, currentValue) {
    console.log(index, currentValue);
    return currentValue + 5;
    });
console.log(newArr2);
//실행결과
// 10 0
// 20 1
// 30 2
// [5, 6, 7]

map 함수의 정의

  • map함수를 사용할 때 index와 currentValue의 위치를 바꿔서 사용하면 역할이 바뀌어 버린다,
    • function (currentValue, index)라고 사용하면 currentValue가 0,1,2 index가 10,20,30이 되어버린다.
  • 즉, 콜백 함수의 제어권을 넘겨받은 코드(map)는 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길 것인지에 대한 제어권을 가진다.

this

  • 별도의 this를 지정하는 방식
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;
};
  • 코드를 보았을때 thisArg 입력되지 않으면 전역객체(window)를 아니면 입력된 thisArg의 값을 지정
  • 메서드를 콜백 함수로 전달한 경우에는 단순히 “함수”로서만 호출된다.
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
// Window { ... } 5 1
// Window { ... } 6 2
  • obj의 메서드를 콜백함수로 넘겨도 obj자체가 가는것이 아닌 함수로서만 전달되므로 this가 전역으로 바인딩 된다.

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

  • 콜백함수 내부에서 this가 객체를 바인딩 하는 여러가지 방법

전통적인 방법

  • 메서드 내부에 this를 저장 후 사용.
var obj1 = {
  name: 'obj1',
  func: function() {
    var self = this;
    return function() {
      console.log(self.name);
    };
  },
};
var callback = obj1.func();
setTimeout(callback, 1000);

→ 실제로는 이렇게 사용하지 않음.

  • 콜백함수 내부에서 this를 사용하지 않는 경우
var obj1 = {
  name: 'obj1',
  func: function() {
    console.log(obj1.name);
  },
};
setTimeout(obj1.func, 1000);

→ 위보단 훨씬 좋지만, 작성한 함수를 this를 이용해 재활용할 수 없게 되어버림

 

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

콜백 지옥 벗어나기

  • 동기 : 현재 실행중인 코드가 완료된 후에야 다음코드를 실행하는 방식
    • CPU에 의해 즉시 처리가 가능한 코드 (연산 등의 시간이 많이 걸린다 하더라도)
  • 비동기 : 현재 실행중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어감
    • 별도의 요청, 실행 대기, 보류 등과 관련된 코드

콜백 지옥

예시코드

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,
  '에스프레소'
);
  • 0.5초 주기마다 커피 목록을 수집하고 출력하는 코드
  • 너무 많은 콜백으로 인해서 들여쓰기가 많아 읽기가 힘들어졌다.

콜백지옥 해결하기

기명 함수 사용하기

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, '에스프레소');
  • 코드의 가독성도 높이고, 순서도 위에서 아래로 내려간다.
  • 하지만 일회성 함수를 전부 변수에 할당하는 것은 비효율적인 일이다.

(ES6) 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에 인자로 넘겨주는 콜백 함수는 바로 실행되지만, 그 내부에 resolve, reject함수를 호출하는 구문이 있을 경우 둘 중 하나가 실행되기 전까지는 then 혹은 catch 구문으로 넘어가지 않는다.

(ES6) 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 함수는 next 메서드를 생성해서 가장 먼저 등장하는 yield에서 함수의 실행을 멈춘다.
  • 그리고 다시 next 메서드를 호출하면 멈췄던 부분에서 시작해서 다음에 등장하는 yield에서 함수실행을 멈춘다.

(ES7) 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();
  • 비동기 작업을 수행하는 함수를 async의 키워드를 사용해서 생성하고, 비동기 작업이 필요한 위치에만 await를 표기하는 것만으로 비동기 작업 처리

 

기타

  • async/await 지옥이라는 것도 존재

async hell

 

 

[번역] 비동기 지옥(async hell) 탈출방법 — Steemit

비동기(async)는 콜백지옥(callback hell)에서 우리를 자유롭게했습니다. 하지만 이는 잘못된 사용으로 인해 비동기 지옥(async hell)의 탄생으로 이어졌습니다. 이 글에서는 async / await 이 무엇인지 설명

steemit.com

 

+ Recent posts