콜백 함수

Soheon Lee·2021년 4월 28일
0

01. 콜백 함수

콜백 함수(callback function)은 다른 코드의 인자로 넘겨주는 함수를 말합니다.

어떤 함수 A를 호출하면서 특정 조건일때 함수 B를 수행하라

라고 한다면, B함수를 호출하는 제어권을 A함수에게 넘겨주었다고 생각할 수 있습니다.
콜백 함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로서 해당 함수의 제어권을 다른 코드에 넘겨준 것입니다.

02. 제어권

1. 호출 시점

setTimeout(function(){ alert("Hello"); }, 3000);

위와 같은 함수가 있다고 가정해보겠습니다.

  • setTimeout은 첫번째 인자로 함수를 넘겨 받습니다.
  • 해당 코드는 3000ms를 기다린 후 Hello를 출력합니다.

setTimeout()을 호출하는 것은 사용자입니다. 하지만 setTimeout()에 넘겨준 함수를 호출하는 것은 setTimeout()이 되는 것이며 제어권 또한 setTimeout()이 가집니다.

2. 인자

콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수를 호출할 때 인자에 어떤 값들을 어떤 순서로 넘길것인지를 제어하는 제어권을 갖고 있는 것이 됩니다.

3. this

콜백 함수도 결국은 함수이기 때문에 기본적으로 this에는 전역객체를 참조합니다. 다만 콜백 함수에 별도의 this가 될 대상을 넘기면 콜백 함수는 해당 값을 참조 하게 됩니다. (3장 공부도 추가적으로 하기)

setTimeout(function () { console.log(this); }, 300); ---- 안넘겼으니 전역객체

[1, 2, 3, 4, 5].forEach(function (x) {
    console.log(this);
});                                                  ---- 역시나 안넘겼으니 전역객체

document.body.innerHTML += '<button id="a"> 클릭 </button>';
document.body.querySelector('#a').addEventListner('click', function (e) {
  console.log(this, e);                              ---- 콜백함수를 호출 할 때 첫번째 인자에 'addEventListener' 메서드의 this를 그대로 넘기기 때문에 HTML 요소를 가리킴.
});

03. 콜백 함수는 함수다

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

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

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

우리가 obj.logValues(1, 2)에서 호출한 것은 obj의 메서드이기 때문에 this는 객체를 가리키고, obj.logValues로 호출한 것은 메서드가 아닌 함수 그 자체이기 때문에, obj와는 직접적인 연관이 없어졌습니다. 따라서, this는 전역객체를 바라보게 됩니다.

04. 콜백 함수 내부의 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); // 1초

해당 함수의 self에다 this를 묶어줬으니, 이제부터 이 함수에서 호출하는 this도 obj1 객체가 된 것입니다.

콜백 함수 내부에서 this를 사용하지 않으려면 다음과 같이 할 수 있습니다.

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

setTimeout(obj1.func, 1000);

여기서는 this를 활용해 다양한 상황에 재활용할 수 없게 되어버렸습니다. 여러 시도 후에 ES5부터는 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);

bind 메서드를 사용하면 코드가 훨씬 간결해지고, 그 의미도 명확하게 다가옵니다.

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

콜백 지옥은 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기가 중복해서 엄청나게 많아지는 것을 말합니다.

setTimeout(() => {
  setTimeout(() => {
    setTimeout(() => {
      // 이런식으로 콜백함수 안에 다시 콜백함수
    }, 1000)
  }, 1000)
}, 1000);

주로 비동기적인 작업을 수행하기 위해 나타나는 형태입니다. 여기서 동기와 비동기를 짚어보겠습니다.

  • 동기(synchronous)적인 코드: 현재 실행 중인 코드가 완료된 후에야 다음 코드를 실행하는 코드
  • 비동기(asynchronous)적인 코드: 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음코드로 넘어가는 코드

별도의 요청, 실행 대기, 보류 등과 관련된 코드는 비동기적인 코드라 볼 수 있다.
이를 처리하기 위한 시도가 버전별로 많이 이루어졌는데, ES6에서는 Promise, Generator 등이 도입되었고, ES2018에서는 async/await가 도입되었습니다.

Promise

ES6에서 추가된 후, 현재 많이 사용되고 있는 Promise 에 대해 먼저 알아보겠습니다.

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(true);
  }, 1000);
}).then((isOk) => {
  setTimeout(() => {
    resolve(isOk);
  }, 1000);
}).catch((err) => {
  console.error("ERROR: ", err);
});

내부에 resolve, reject 함수를 호출하는 구문이 있는데, 둘 중 하나가 실행되기 전까지는 다음(then)이나 오류(catch)구문으로 넘어가지 않습니다.일반적으로 비동기 작업이 완료될 때, 그때서야 resolve, reject를 호출해서 다음 작업을 수행하는 방식입니다.

Generator

함수 뒤에 *를 이용하여 구현합니다. Generator 함수를 실행하면 Iterator가 반환되며, Iterator는 next라는 메소드를 가지고 있습니다. 이 next 메소드를 호출하면 Generator 함수 내부에서 가장 먼저 등장하는 yield에서 함수의 실행을 잠시 멈춥니다. 이후 다시 next 메서드를 호출하면 앞서 멈췄던 부분부터 시작해서 그다음에 등장하는 yield에서 함수의 실행을 멈춥니다.
비동기 작업이 완료되는 시점마다 next 메서드를 호출하는 방식을 이용합니다.

async/await

비동기 작업을 수행하고자 하는 함수 앞에 async를 표기하고, 함수 내부에서 실질적으로 비동기 작업이 필요한 위치마다 await를 표기하는 것으로 뒤의 내용을 Promise로 자동 전환합니다. 해당 내용이 resolve된 이후에 다음으로 진행합니다.
Promise에서 then을 사용했을 때와 비슷한 방식으로 진행됩니다.

06. 정리

  1. 콜백 함수는 다른 코드에 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수를 말하며 제어권은 넘겨 받은 코드는 콜백 함수를 언제 실행할 것인지, 어떤 값을 넘겨줄 것인지를 결정합니다.
  2. 어떤 함수에 인자로 메서드를 전달 하더라도 이는 결국 함수로서 실행합니다.
  3. 비동기 제어를 위해 콜백 함수를 사용하다 보면 콜백 지옥에 빠지기 쉽습니다. Promise, Generator, async/await등 콜백 지옥을 작성하지 않을 수 있는 방법들을 익히는 방향으로 공부해야합니다.
profile
Hello, World

0개의 댓글