자바스크립트 입문_콜백지옥(callback hell), 비동기(async) 제어

자바스크립트 2021. 5. 8. 17:25

(본 포스팅은 위키북스의 '코어자바스크립트' 책을 공부하면서 작성되었습니다_내돈내산)

 

  • 콜백지옥(callback hell): 콜백함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상, 주로 비동기 처리를 위한 작업에서 많이 발생하며 가독성과 유지보수 측면에서 좋지못함.
  • 비동기(asyncronous): 현재 실행중인 코드의 완료 여부와 무관하게 즉시 다음코드로 넘어감. 사용자의 요청에 의해 특정 시간이 경과되기 전까지 어떤 함수의 실행을 보류한다거나(setTimeout), 사용자의 직접적인 개입이 있을 때 어떤함수를 실행하도록 대기한다거나(addEventListener), 웹브라우저 자체가 아닌 별도의 대상에 무언가를 요청하고 그에대한 응답이 왔을 때 비로소 어떤 함수를 실행하도록 대기하는 등(XMLHttpRequest), 별도의 요청, 실행대기, 보류 등과 관련된 코드

- 콜백지옥 예시 -

setTimeout(function (name) {
  let 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, '에스프레소');

 

  • 들여쓰기가 더해지고, 코드를 읽는방향(값이 전달되는 순서)가 '위에서 아래'가 아닌 '아래에서 위'로 되어있어서 가독성이 매우 떨어지고 어색하게 보여짐.

- 콜백지옥 해결: 기명함수로 전환 -

let coffeeList = '';

const addEspresso = function (name) {
  coffeeList = name;
  console.log(coffeeList); // "에스프레소"
  setTimeout(addAmericano, 500, '아메리카노');
};

const addAmericano = function (name) {
  coffeeList += ', ' + name;
  console.log(coffeeList); // "에스프레소, 아메리카노"
  setTimeout(addMocha, 500, '카페모카');
};

const addMocha = function (name) {
  coffeeList += ', ' + name;
  console.log(coffeeList); // "에스프레소, 아메리카노, 카페모카"
  setTimeout(addLatte, 500, '카페라떼');
};

const addLatte = function (name) {
  coffeeList += ', ' + name;
  console.log(coffeeList); // "에스프레소, 아메리카노, 카페모카, 카페라떼"
};

setTimeout(addEspresso, 500, '에스프레소');
  • 가독성이 올라갔음. 위에서 아래로 순서대로 읽어내려갈 수 있도록 구조가 바뀜.
  • promise, Generator, async/await 등을 이용해 개선이 가능함.

- Promise를 이용한 비동기 작업의 동기적 표현-

new Promise(function (resolve) {
  setTimeout(function () {
    const name = '에스프레소';
    console.log(name);
    resolve(name);
  }, 500);
}).then(function (prevName) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      const name = prevName + ', 아메리카노';
      console.log(name);
      resolve(name);
    }, 500);
  });
}).then(function (prevName) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      const name = prevName + ', 카페모카';
      console.log(name);
      resolve(name);
    }, 500);
  });
}).then(function (prevName) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      const name = prevName + ', 카페라떼';
      console.log(name);
      resolve(name);
    }, 500);
  });
})
  • new 연산자와 함꼐 호출한 Promise의 인자로 넘겨주는 콜백함수는 호출시 바로 실행되지만 resolve 또는 reject함수를 호출하지 않으면 then을 통해 다음으로 넘어가지 않음.(resolve또는 reject가 가지고 있는 인수는 다음 프로미스 구문으로 전달됨)
  • 비동기 작업이 완료될때 resolve또는 reject가 호출되는 방법으로 비동기 작업을 동기식으로 표현하는 방법임.

- Generator를 이용한 비동기 작업의 동기적 표현 -

const addCoffee = function (prevName, name) {
  setTimeout(function () {
    coffeeMaker.next(prevName ? prevName + ', ' + name : name); // 삼항연산자
  }, 500);
};

const coffeeGenerator = function* () { // Generator 함수
  const espresso = yield addCoffee('', '에스프레소');
  console.log(espresso);
  
  const americano = yield addCoffee(espresso, '아메리카노');
  console.log(americano);
  
  const mocha = yield addCoffee(americano, '카페모카');
  console.log(mocha);
  
  const latte = yield addCoffee(mocha, '카페라떼');
  console.log(latte);
};

const coffeeMaker = coffeeGenerator();
coffeeMaker.next(); // 처음 coffeeMaker함수를 실행시키기 위한 next 메소드
  • 앞에 * 이 붙은 Generator 함수를 실행하면 Iterator 가 반환되고 Iterator는 next 메서드를 가지고 있음.
  • next 메서드가 호출되면 Generator 함수의 제일 첫번째 yield에서 함수의 실행을 멈춤. 다음 next메서드를 한번 더 호출하면 그 다음 yield 에서 멈추고 이를 반복한다. 비동기 작업이 완료되는 시점마다 next 메서드를 호출하면 Generator가 위에서 아래로 순차적으로 진행됨.

- Promise + Async/await 를 이용한 비동기 작업의 동기적 표현 -

const addCoffee = function (name) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(name);
    }, 500);
  });
};

const coffeeMaker = async function () {
  let coffeeList = '';
  const _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를 표기하는것으로 뒤의 내용을 Promise로 자동전환하고, 해당내용이 resolve된 이후에 다음으로 진행하게 됨.
admin