💻 JavaScript/기초 문법

JS 문법 종합: 콜백 함수와 동기/비동기

seheej 2024. 8. 6. 20:58

(1) 콜백 함수란?

콜백 함수는 다른 함수의 인자로 전달되어, 필요할 때 실행되는 함수입니다. 제어권을 다른 함수에게 넘기는 것이 특징입니다.

콜백 함수의 특징

  1. 인자로 넘겨줌: 콜백 함수는 forEach, setTimeout 등 다른 함수의 인자로 전달됩니다.
  2. 제어권 위임: 콜백 함수는 호출 시점과 방식이 인자를 받는 함수에 의해 결정됩니다.
  3. 콜백 지옥: 콜백 함수를 중첩하여 사용하면 코드의 가독성이 떨어지고 유지보수가 어려워집니다.
// setTimeout
setTimeout(function() {
  console.log("Hello, world!");
}, 1000);

// forEach
const numbers = [1, 2, 3, 4, 5];
numbers.forEach(function(number) {
  console.log(number);
});

 

제어권

콜백 함수를 넘겨받은 함수가 실행 시점과 인자를 제어합니다. 예를 들어 setInterval의 경우, 일정 시간 간격으로 콜백을 호출합니다.

var count = 0;
var timer = setInterval(function() {
  console.log(count);
  if(++count > 4) clearInterval(timer);
}, 300);

 

콜백 함수와 this

콜백 함수에서의 this는 호출 시점에 따라 달라집니다. forEach와 같은 메서드는 전역 객체를 가리킬 수 있습니다.

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

// Method로 호출
obj.logValues(1, 2);

// Callback으로 전달
[4, 5, 6].forEach(obj.logValues);

 

콜백 함수의 this 바인딩

콜백 함수에서 this를 바인딩하는 방법은 bind 메서드를 사용하는 것입니다.

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

// bind를 사용하여 obj1로 this 바인딩
setTimeout(obj1.func.bind(obj1), 1000);

var obj2 = { name: 'obj2' };
// bind를 사용하여 obj2로 this 바인딩
setTimeout(obj1.func.bind(obj2), 1500);

 

 

(2) 콜백 지옥과 비동기 제어

콜백 지옥

콜백 지옥은 비동기 작업에서 콜백을 중첩하여 사용하면서 발생하는 문제입니다. 코드의 들여쓰기 수준이 깊어지고, 가독성과 유지보수성이 떨어집니다.

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

 

동기 vs 비동기

  1. 동기(Synchronous): 코드가 작성된 순서대로 실행됩니다. 현재 작업이 완료될 때까지 다음 작업이 대기합니다.
  2. 비동기(Asynchronous): 현재 작업이 완료되지 않아도 다음 작업이 실행됩니다. 예를 들어, setTimeout이나 addEventListener가 비동기적입니다.

콜백 지옥 해결 방법

콜백 지옥을 해결하기 위해 Promise, Generator, async/await 등의 비동기 제어 방법을 사용할 수 있습니다.

 

Promise

Promise는 비동기 작업의 결과를 처리하기 위한 객체입니다. then, catch 메서드를 사용하여 비동기 작업이 완료된 후의 동작을 정의할 수 있습니다.

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

 

 

Generator

Generator 함수는 yield 키워드를 사용하여 실행을 일시 중지하고, next 메서드 호출을 통해 실행을 재개할 수 있습니다. 비동기 작업의 진행을 제어할 수 있습니다.

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

 

 

Async/Await

async/await는 비동기 코드를 동기 코드처럼 작성할 수 있게 해줍니다. await는 Promise가 해결될 때까지 실행을 일시 중지합니다.

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

이렇게 Promise와 async/await를 활용하면 비동기 작업을 동기적으로 작성할 수 있어 가독성을 높이고 콜백 지옥을 피할 수 있습니다.