[자바스크립트] 콜백 함수(Callback Function)

minidoo·2020년 11월 4일
0

자바스크립트 / NodeJS

목록 보기
16/27
post-thumbnail

콜백함수

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

콜백 함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다.
콜백 함수를 위임받은 코드는 자체적인 내부 로직에 의해 이 콜백 함수를 적절한 시점에 실행한다.

setInterval 함수는 첫 번째 매개변수로 익명함수를 두 번째 매개변수로 숫자를 받는다.
setInterval 이라고 하는 ''다른 함수'에 익명함수를 넘겨주면 제어권도 함께 넘겨받은 setInterval이 스스로의 판단에 따라 적절한 시점에 이 익명 함수를 실행한다.

이처럼, 콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수의 호출 시점에 대한 제어권을 가진다.

콜백함수의 this

map 메서드는 첫 번째 인자로 callback 함수를 받고, 두 번째 인자로 콜백 함수 내부에서 this로 인식할 대상 을 특정할 수 있다. 두 번째 인자는 생략할 수 있으며 이 경우 전역객체(브라우저의 경우 윈도우 객체)를 가리킨다.

위의 사진에서 두 번째 인자로 'callback'이라는 string 값을 전달했기 때문에 this는 'callback'이 찍힌다.

  • this를 특정하지 않은 경우
[10, 20, 30].map(function(curr, index) {
  console.log(curr, index);
  console.log(this);
  
  return curr + 5;
});

<output>
10 0
Window {...}
20 1
Window {...}
30 2
Window {...}
  • this를 특정한 경우
[10, 20, 30].map(function(curr, index) {
  console.log(curr, index);
  console.log(this);
  
  return curr + 5;
}, 'callback');

<output>
10 0
'callback'
20 1
'callback'
30 2
'callback'

콜백함수는 함수다

위의 사진에서 logValues는 obj 객체의 메서드로 정의됐다.

obj.logValues(1, 2) 는 이름 앞에 점이 있기 때문에 메서드로서 호출한 것이다. 따라서 this는 객체 자체 obj를 가리키고, 인자로 넘어온 1과 2가 출력된다.

setTimeout(obj.logValues, 1000) 은 메서드를 setTimeout의 콜백함수로서 전달했다. 즉, obj를 this로 하는 메서드를 그대로 전달한 것이 아니라 obj.logValues가 가리키는 함수만 전달한 것이다. 따라서 this는 전역 객체를 바라보게 된다.

{ vals: Array(3), logValues: f logValues() } [1, 2, 3] 1 2
Window { window: Window, self: Window, document: HTMLDocument, name: "" ... }

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

전통적인 방식 1

self 변수에 this를 담고, 익명함수를 선언함과 동시에 반환하는 방식이다.
이 방식은 실제로 this를 사용하지 않을 뿐더러 번거롭다.

전통적인 방식 2

this 자리에 객체 자체를 넣는 방식이다.
훨씬 간결하고 직관적이지만 this를 이용해 다양한 상황에 재활용할 수 없게 되었다.

위의 예시에서 우리는 obj.vals 값에 각 [ 1, 2, 3 ]과 [ 4, 5, 6 ]이 출력되길 바란다.
하지만, 실제 결과는 두 결과 값 모두 [ 1, 2, 3 ]이 나온다. 처음부터 바라볼 객체를 명시적으로 obj로 지정했기 때문에 어떤 방법으로도 다른 객체를 바라보게끔 할 수가 없다.

{ vals: Array(3), logValues: f logValues() } [ 1, 2, 3 ]
{ vals: Array(3), logValues: f logValues() } [ 1, 2, 3 ]

bind, call, apply 메서드 활용

bind, call, apply 메서드는 첫 번째 매개변수에 this로 지정할 값을 넣는다.
bindcall 과 비슷하지만 즉시 호출하지 않고 넘겨 받은 this를 바탕으로 새로운 함수를 반환한다. apply 는 두 번째 매개변수부터 배열로 받는다.

{ vals: Array(3), logValues: f logValues() } [ 1, 2, 3 ]

콜백 지옥과 비동기 제어

동기적인 코드는 현재 실행 중인 코드가 완료된 후, 다음 코드를 실행하는 방식이다.
CPU의 계산에 의해 즉시 처리 가능한 대부분의 코드는 동기적인 코드이며 실생활 예로 은행이 동기적인 곳이다.

비동기적인 코드는 현재 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식이다.
setTimeout이나 addEventListener처럼 별도의 요청이나 실행 대기, 보류와 관련된 코드는 비동기 코드이다.

위의 사진은 콜백 지옥의 예시이다. 목적 달성에는 지장이 없고 짧은 코드라 덜 복잡해보이지만, 코드가 길어질 수록 들여쓰기 수준이 과도하게 깊어질 것이다.

오랜 시간동안 자바스크립트는 비동기적인 작업을 동기적으로 보여주기 위해 노력해왔고, 그 대표적인 방법이 ES6의 Promise와 ES2017의 async/await 이다.

Promise

new 연산자와 함께 호출한 Promise의 인자로 넘겨주는 콜백 함수는 호출할 때 바로 실행되지만 그 내부에 resolve 또는 reject 함수를 호출하는 구문이 있을 경우, 둘 중 하나가 실행되기 전까지는 다음(then) 또는 오류 구문(catch)으로 넘어가지 않는다.

따라서 Promise 안의 작업이 완료된 후에야 다음 코드(then, catch)를 실행하는 동기적 표현이 가능하다.

function delay(n) {
  let answer = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(n);
    }, 1000);
  })
  return answer;
}

delay(1).then(result => {
  console.log(result);
  return delay(2);
}).then(result => {
  console.log(result);
  return delay(3);
}).then(result => {
  console.log(result);
  return delay(4);
}).then(result => {
  console.log(result);
  return delay(5);
}).then(result => {
  console.log(result);
})

async/await

ES2017에서는 가독성이 뛰어나면서도 작성법이 간단한 새로운 기능인 async/await 가 추가되었다.
비동기 작업을 수행하고자 하는 함수 앞에 async를 표기하고, 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await를 표기한다.
주로 Promise는 끝나는 시점을 알려주는 용도로, async/await는 해당 구문이 완료될 때까지 기다리는 용도로 사용된다. 아래의 코드는 반복되는 코드를 for 문으로 바꿔준 것이다.

function delay(n) {
  let answer =  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(n);
    }, 1000);
  })
  return answer
}

let numList = async function() {
  for(let n=0; n<6; n++) {
    let result = await delay(n);
    console.log(result); 
  }
}

numList();

참고 서적

코어 자바스크립트(Core JavaScript)
https://book.naver.com/bookdb/book_detail.nhn?bid=15433261

1개의 댓글

comment-user-thumbnail
2021년 5월 5일

좋은 정보 감사합니다. 그런데 궁금한게 있는데요 전통적인 방식(2) - 문제점에서 두번째 setTimeout() 메소드의 기대값이 [4, 5, 6] 인가여...??

답글 달기