[JS] callback 그리고 Promise를 사용하는 이유 (1)

jiseong·2022년 5월 19일
2

T I Learned

목록 보기
249/291
post-thumbnail

callback

과거에는 callback만을 사용하여 거대한 파일을 읽고 응답이 나올 때까지 기다리는 방식(비동기 처리)으로 코드를 작성할 수 있었다. 그러나 callback만을 사용하여 비동기 처리를 한다면 소위 말하는 콜백 지옥(callback hell)에 빠지게 되는 문제점이 생긴다.

다음과 같은 코드가 있다고 가정해보자.

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err);
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename);
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err);
        } else {
          console.log(filename + ' : ' + values);
          aspect = values.width / values.height;
          widths.forEach(
            function (width, widthIndex) {
              height = Math.round(width / aspect);
              console.log('resizing ' + filename + 'to ' + height + 'x' + height);
              this.resize(width, height).write(dest + 'w' + width + '_' + filename, function (err) {
                if (err) console.log('Error writing file: ' + err);
              });
            }.bind(this)
          );
        }
      });
    });
  }
});

코드의 흐름파악

callback의 무한 중첩문제는 코드의 흐름파악이 어렵다는 점이다. 그뿐만 아니라 예외처리 코드까지 더해지면 더 복잡한 코드가 되어버려 유지보수가 어려워지게 되며 코드의 뎁스가 점점 더 깊어질수록 개발자들의 눈은 피곤해지고 이전의 코드는 까먹게 되는 것이 흔한일이 되어버린다.

더 큰 문제는 다음과 같은 코드이다.

doA( function(){
	doC();
  
    doD( function(){
      doF();
    } )
  
    doE();
} );
doB();

모든 함수가 비동기라고 생각하여 A -> B -> C -> D -> E -> F 순서로 작동된다고 생각했는데 만약 A, D가 비동기가 아니라면 어떻게 될까?

이제는 A -> C -> D -> F -> E -> B 순서로 동작하게 될 것이다. 이처럼 내부 구조를 모르는 서드파티 라이브러리함수를 사용하여 콜백함수를 전달해줬지만 실제로 동기함수였다면 예상했던 순서와 다르게 동작하여 예상했던 순서를 보장받지 못할 수도 있는 단점이 있다.

그런데 callback은 더 심각한 문제가 있다.

제어 역전(Inversion Of control)

// A
ajax( "..", function(..){
	// C
} );
// B

A와 B는 자바스크립트 메인 프로그램의 제어를 직접 받으며 실행되지만 C는 서드파티 라이브러리함수의 제어하에 나중에 실행된다. 이처럼 서드파티 라이브러리함수에 의존하게 되면서 제어권의 주체가 뒤바뀌는 것을 제어 역전이라고 한다.

제어 역전의 가장 큰 문제는 믿음이다.
서드파티 라이브러리 함수가 어떻게 동작되는지 모르지만 그저 개발자가 생각했던 의도대로 잘 작동한다고 생각하며 사용할 것이다.

하지만 이는 버그를 심어놓은 것과 같다. 콜백 호출시 발생한 오류는 다양하며 경우의 수는 다음과 같다.

  • 콜백을 너무 일찍 부르는 경우
  • 콜백을 너무 늦게 부르는 경우
  • 콜백을 너무 적게 또는 많이 부르는 경우
  • 발생할지 모르는 에러/예외를 무시하는 경우

잘 작동하던 코드가 콜백으로 인해 어느 날 갑자기 동작하지 않는다면 서드파티 라이브러리 함수에 대한 믿음이 깨지게 되며 믿음이 깨지는 순간 이러한 경우의 수들을 방어하기 위한 코드등이 추가되면서 코드가 비대해지게 된다.

이처럼 callback은 원하는 바를 이룰 수 있게 해주지만 그것을 얻기 위해 소비해야 하는 것이 훨씬 많아진 함수가 되어버린 것이다. 이러한 문제점을 경험한 사람들은 내장 API 또는 다른 언어에서 지원하는 방법을 바래왔는데, 마침내 ES6에서 기막힌 해결책이 등장했다.

그것은 바로 우리가 많이 사용해오고 있는 Promise라는 객체이다.

(Promise는 내일 정리)


Reference

0개의 댓글