Javascript

(JavaScript) Callback 함수

JJeongHyun 2023. 4. 4. 21:37
반응형

목차)

  1. Callback  함수란?
  2. Callback 함수의 필요성
  3. Callback 함수를 만드는  법
  4. Callback 지옥

 

Callback 함수의 정의

  • 다른 함수가 실행을 끝난 뒤에 실행되는 함수
  • 함수 안에서 어떤 특정한 시점에 호출되는 함수
  • 다른 함수의 매개변수로 함수를 전달하고, 어떠한 이벤트가 발생한 후 매개변수로 전달한 함수가 다시 호출되는 것
  • 파라미터로 함수를 전달받아, 함수의 내부에서 실행하는 함수
  • 예시)
    • 식당에 웨이팅이 걸려서 식당 앞에 있는 어플로 핸드폰번호를 적어 두고 입장 대기를 걸어둔다
    • 이후 다른 식당이나 쇼핑 등 마냥 기다리는 게 아닌 다른 행동을 하면서 기다린다
    • 적어둔 핸드폰번호로 입장하라는 말이 올 때 식당으로 가는 것이 callback함수가 호출되는 시점이라고 할 수 있다

 

Callback 함수의 필요성

  • 해당 태스크가 끝난 직후에 실행할 함수를 callback 함수라고 할 수 있다
  • callback은 태스크가 끝나기 전에 함수가 실행되지 않는 걸 방지하고자 사용
  • 비동기 JS코드를 여러 문제, 에러들로 부터 안전하게 실행할 수 있게 해 준다
  • 비동기 방식으로 작성된 함수를 동기적으로 처리하기 위해 사용
    • 순차적인 처리가 필요할 때 콜백함수를 사용함으로써 동기적으로 처리가 가능 해진
const plus = (a, b) => {
  let sum = a + b;
  return sum;
};

const cbFunc = (result) => {
  console.log(result);
};

cbFunc(plus(2, 5));
  • 두 수의 합을 반환시켜는 함수를 호출해서 그 결괏값을 콘솔창에 띄우기 위한 코드
  • 콘솔을 띄우는 cbFunc함수는 plus함수의 실행이 종료되서야 실행된다, 즉 그 실행이 끝이 날 때까지 기다려야 한다
  • 위처럼 간단한 코드라면 상관없지만, 함수 내 코드가 복잡해서 오래 걸린다면 싱글 스레드 기반의 JS는 모든 동작이 멈춰버릴 것이다
const plus = (a, b,cb) => {
  let sum = a + b;
  cb(sum)
};

const cbFunc = (result) => {
  console.log(result);
};

plus(2,5, cbFunc);
  • 이처럼 callback함수를 이용해서 구현한다면 필요할 때 plus 함수를 호출해서 처리가 끝났을 시점에 결과를 콘솔창에 띄우는 함수를 호출할 수 있다
  • 이에 처리가 끝이날 때까지 JS의 모든 동작을 멈추게 하면서 까지 기다리를 필요가 없어짐으로써 효율이 올라간다

 

Callback 함수 만들어보기 

  • 변수나 데이터 구조안에 담을 수 있어야 한다
    • JS는 변수나 구조체, 객체등에 함수를 담을 수 있다
  • 파라미터(매개변수)로 전달할 수 있다
  • 반환값으로 사용될 수 있다
  • 런타임에 생성될 수 있다
const hello = (name, callback) => {
  console.log(`Hi, ${name}`);
  callback();
};

const callbackTest = () => {
  console.log("callback call");
};

hello("jjh", callbackTest);

// 결과
// Hi, jjh
// callback call
  • 두 개의 함수를 생성하고 hello함수를 호출하면서 callbackTest라는 함수를 매개변수로 전달
  • hello함수가 첫 번째로 전달받은 매개변수를 사용해서 console.log() 라인을 실행
  • 이후 마지막에 두 번째로 전달받은 callbackTest를 callback()으로 실행
const a = () => {
  console.log("1");
};

const b = () => {
  console.log("2");
};
const c = () => {
  console.log("3");
};

a();
b();
c();

// 결과
// 1
// 2
// 3
  • 위에서부터 아래로 순서대로 a() 함수부터 c() 함수 순으로 실행이 될 것이다
  • 따라서 1,2,3 순으로 콘솔의 결과가 나온다
const a = () => {
  console.log("1");
};

const b = () => {
  setTimeout(() => {
    console.log("2");
  }, 1000);
};
const c = () => {
  console.log("3");
};

a();
b();
c();

// 결과
// 1
// 3
// 2 << 3이 출력되고 1초후에 출력
  • a() 함수가 실행이 되고 콘솔 1이 찍히고, b() 함수가 호출되면서 setTimeout()이 호출된다. 웹 브라우저 런타임 환경을 기준으로 setTimeout() 함수는 Web APIs 중 하나이기에 호출되면서  싱글 스레드 JS엔진 call stack에서 제거된다
  • 이후 Web APIs 내부에서 timer가 작동하는 도중에 c() 함수가 호출되면서  콘솔에 3이 출력된다
  • 1000ms 만큼 timer가 작동 후 setTimeout() 함수 내에 있는 callback함수가 task queue에 밀어져서 들어간다
  • JS엔진에서 call stack이 비어져있는 걸 인지한 event loop가 queue 내부에 있는 callback 함수를 stack으로 밀어 넣어 줌으로써 콘솔창에 2가 출력된다

 

Callback 지옥

  • callback함수를 익명함수로 전달하는 과정에서 또 다시 다른 callback함수를 호출하는 반복적인 코드
  • 이에 코드의 depth가 깊어지고 그만큼 가독성 부분에서 매우 좋아지지 않고 따라서 코드 수정 또한 어려워진다
const 아반떼 = (callback) => {
  setTimeout(() => {
    console.log("아반떼 go");
    callback();
  }, 1000);
};
const 소나타 = (callback) => {
  setTimeout(() => {
    console.log("소나타 go");
    callback();
  }, 3000);
};
const 그랜저 = (callback) => {
  setTimeout(() => {
    console.log("그랜저 go");
    callback();
  }, 2000);
};
console.log("start");
아반떼(() => {
  소나타(() => {
    그랜저(() => {
      console.log("end");
    });
  });
});

// 결과
// start
// 아반떼 go << start가 출력되고 1초후 출력
// 소나타 go << 아반테 go가 출력되고 3초후 출력
// 그랜저 go << 소나타 go가 출력되고 2초후 출력
// end << 그랜저 go 출력되고 바로 출력
  • 콘솔창에 start가 출력이 된다
  • 아반떼 함수가 실행되면서 setTimeout() 내 callback함수가 1000ms(1초) 후에 콘솔창에 "아반떼 go"를 출력한다
  • 이후 넘겨받은 익명함수(callback함수)를 실행한다
  • 소나타 함수가 실행되면서 setTimeout() 내 callback함수가 3000ms(3초) 후에 콘솔창에 "소나타 go"를 출력한다
  • 이후 넘겨받은 익명함수(callback함수)를 실행한다
  • 그랜져 함수가 실행되면서 setTimeout() 내 callback함수가 2000ms(2초) 후에 콘솔창에 "그랜져 go"를 출력한다
  • 마지막으로 콘솔창에 end를 출력해 준다