[Javascript] 콜백지옥

한별·2024년 4월 29일

Javascript

목록 보기
23/25

콜백 함수를 중첩해서 사용하여 들여쓰기 지옥에 빠지는 것.

콜백함수란?
어떤 함수의 파라미터로 사용되는 함수
순차적으로 함수를 실행하고 싶을 때 사용한다.

주로 이벤트 처리나 서버 통신과 같은 비동기 작업을 수행할 때 발생한다.
여러개의 비동기 작업을 순차적으로 실행하고 싶을 때, 비동기 함수를 콜백함수로 넣게 되면 콜백 지옥이 발생하게 되는 것이다.
가독성이 떨어지고, 유지보수가 어려워진다.

setTimeout(
  function (num) {
    var str = num;
    console.log(str);

    setTimeout(
      function (num) {
        str += ', ' + num;
        console.log(str);

        setTimeout(
          function (num) {
            str += ', ' + num;
            console.log(str);

            setTimeout(
              function (num) {
                str += ', ' + num;
                console.log(str);
              },
              1000,
              4
            );
          },
          1000,
          3
        );
      },
      1000,
      2
    );
  },
  1000,
  1
);

// 1초 - 1
// 2초 - 1, 2
// 3초 - 1, 2, 3
// 4초 - 1, 2, 3, 4

보기만 해도 Hell이다...^^;

해결 방안

1. [참고] 기명함수로 변환

var str = '';

const printOne = function (num) {
  str = num;
  console.log(str);
  setTimeout(printTwo, 1000, 2);
};
const printTwo = function (num) {
  str += ', ' + num;
  console.log(str);
  setTimeout(printThree, 1000, 3);
};
const printThree = function (num) {
  str += ', ' + num;
  console.log(str);
  setTimeout(printFour, 1000, 4);
};
const printFour = function (num) {
  str += ', ' + num;
  console.log(str);
};

setTimeout(printOne, 1000, 1);

가독성은 좋지만, 한 번만 쓸 함수를 위한 메모리 공간이 낭비된다.
(근본적인 해결법이 아님)

JS에서 제공하는 비동기 작업의 동기적 표현이 필요하다.
Promise, Generator, async/await가 있다.

2. Promise

Promise의 콜백함수는 바로 실행된다.
콜백함수 내부에 resolve 또는 reject 함수를 호출하는 구문이 실행되기 전까지 다음 then / catch 로 넘어가지 않는다.

new Promise(function (resolve) {
  setTimeout(function () {
    var str = 1;
    console.log(str);
    resolve(str);
  }, 1000);
})
  .then(function (prevString) {
    return new Promise(function (resolve) {
      setTimeout(function () {
        str = prevString + ', 2';
        console.log(str);
        resolve(str);
      }, 1000);
    });
  })
  .then(function (prevString) {
    return new Promise(function (resolve) {
      setTimeout(function () {
        str = prevString + ', 3';
        console.log(str);
        resolve(str);
      }, 1000);
    });
  })
  .then(function (prevString) {
    return new Promise(function (resolve) {
      setTimeout(function () {
        str = prevString + ', 4';
        console.log(str);
        resolve(str);
      }, 1000);
    });
  });

반복되는 코드를 함수화하면 깔끔한 코드가 작성된다.

const addNumber = function (num) {
  return function (prevString) {
    return new Promise(function (resolve) {
      setTimeout(function () {
        str = prevString ? `${prevString}, ${num}` : num;
        console.log(str);
        resolve(str);
      }, 1000);
    });
  };
};

addNumber(1)()
  .then(addNumber(2))
  .then(addNumber(3))
  .then(addNumber(4));

3. Generator

제너레이터 함수임을 나타내기 위해서 function 뒤에 *를 붙인다.
yield에서 멈추고 next를 호출하면 다시 실행된다.

const addNumber = function (prevString, num) {
  setTimeout(function () {
    numberGenerator.next(prevString ? `${prevString}, ${num}` : num);
  }, 1000);
};

var numberGenerator = function* () {
  const one = yield addNumber('', 1);
  console.log(one);
  const two = yield addNumber(one, 2);
  console.log(two);
  const three = yield addNumber(two, 3);
  console.log(three);
  const four = yield addNumber(three, 4);
  console.log(four);
};

var numberGenerator = numberGenerator();
numberGenerator.next();

4. async / await

async 키워드를 붙인 함수에서
await 키워드를 만난 메서드는 동기적으로 작동한다. (메서드가 끝날 때까지 대기)

const addNumber = function (num) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(num);
    }, 1000);
  });
};

var numberGenerator = async function () {
  var str = '';
  var _addNumber = async function (name) {
    str += (str ? ', ' : '') + (await addNumber(name));
  };
  await _addNumber(1);
  console.log(str);
  await _addNumber(2);
  console.log(str);
  await _addNumber(3);
  console.log(str);
  await _addNumber(4);
  console.log(str);
};

numberGenerator();
profile
글 잘 쓰고 싶어요

0개의 댓글