5. 콜백 지옥과 비동기 제어

 1) 콜백 지옥: 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 너무 깊어지는 현상

  -비동기적 작업에서 콜백 지옥이 자주 등장

 

 2) 비동기: 동기의 반대말로, 현재 코드 실행 완료 여부와 무관하게 다음 코드 실행

  -동기적인 코드는 현재 실행 중인 코드가 완료된 후 다음 코드 실행

  -CPU 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적인 코드

  -setTimeout, addEventListener, XMLHttpRequest 등 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 비동기적 코드

  -웹의 복잡도에 따라 비동기적 코드의 비중이 높아졌고 콜백 지옥에 빠지기 쉬워짐

 

 3) 콜백 지옥 예시

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

// 결과
// 에스프레소
// 에스프레소, 아메리카노
// 에스프레소, 아메리카노, 카페모카
// 에스프레소, 아메리카노, 카페모카, 카페라떼

  -0.5초마다 커피 이름을 하나씩 추가하여 출력

  -들여쓰기가 깊고, 값이 전달되는 순서가 아래에서 위로 어색하게 향함

 

 4) 해결방법

  (1) 기명함수로 변환

var coffeeList = '';

var addEspresso = function (name) {
  coffeeList = name;
  console.log(coffeeList);
  setTimeout(addAmericano, 500, '아메리카노');
};
var addAmericano = function (name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
  setTimeout(addMocha, 500, '카페모카');
};
var addMocha = function (name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
  setTimeout(addLatte, 500, '카페라떼');
};
var addLatte = function (name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
};

setTimeout(addEspresso, 500, '에스프레소');

  -가독성이 높아지고, 위에서 아래로 읽어내려가는대로 출력되어 어색함도 사라짐

  -일회성 함수를 매번 변수에 할당하는 것이 복잡해 보일 수 있음

 

  (2) promise

   -방법 1

new Promise(function (resolve) {
  setTimeout(function () {
    var name = '에스프레소';
    console.log(name);
    resolve(name);  // promise가 fufilled(성공)일 때, name 반환
  }, 500);
}).then(function (prevName) {  // promise.then에서 위의 resolve 결과를 받아 인자로 사용
  return new Promise(function (resolve) {
    setTimeout(function () {
      var name = prevName + ', 아메리카노';  // 인자로 받은 초기 name에 '아메리카노' 추가
      console.log(name);
      resolve(name);  // 아메리카노가 추가된 name을 다시 반환
    }, 500);
  });
}).then(function (prevName) {  // 위에서 resolve로 넘겨준 아메리카노가 추가된 name을 인자로 사용
  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);
  });
});

   -resolve 또는 reject가 있으면 둘 중 하나가 실행되기 전까지 다음 then 또는 catch로 넘어가지 않으므로 비동기 작업의 동기적 표현이 가능

 

   -방법 2

var addCoffee = function (name) {
  return function (prevName) {
    return new Promise(function (resolve) {
      setTimeout(function () {
        var newName = prevName ? (prevName + ', ' + name) : name;
        console.log(newName);
        resolve(newName);
      }, 500);
    });
  };
};

addCoffee('에스프레소')()
  .then(addCoffee('아메리카노'))
  .then(addCoffee('카페모카'))
  .then(addCoffee('카페라떼'));

 

   -방법 1에서 반복적이던 내용을 함수화해서 짧게 표현

 

  (3) Generator

var addCoffee = function(prevName, name) {
  setTimeout(function () {
    coffeeMaker.next(prevName ? prevName + ', ' +name : name);
  }, 500);
};
var coffeeGenerator = function* () {  // Generator 함수
  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();

   -Generator 함수를 실행하면 Iterator가 반환되며

   -Iterator는 next라는 메서드를 가짐

   -next를 실행하면 Generator 함수 내 가장 첫번째 yield에서 함수의 실행을 멈춤

   -이후 다시 next 메서드 호출하면 멈췄던 부분부터 시작해서 다음 yield에서 함수의 실행을 멈춤

   -비동기 작업이 완료되는 시점(console에 name 출력)마다 next를 호출하면 Generator 함수가 위에서 아래로 순차적으로 진행됨

 

  (4) promise + async/await

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

   -비동기 작업을 수행하고자 하는 함수 앞에 async 표기

   -함수 내부에 실질적으로 비동기 작업이 필요한 위치마다 await 표기

   -await를 쓰려면 함수 앞에 반드시 async가 표기되어 있어야 함

+ Recent posts