콜백 지옥

post-thumbnail

콜백 지옥과 비동기 제어

  1. 콜백지옥이란

    1. 콜백 함수를 익명 함수(아래 이미지로 살짝 보시면…)로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 헬 수준인 경우

    2. 주로 이벤트 처리서버 통신과 같은 비동기적 작업을 수행할 때 발생

    3. 뭐가문제일까? 가독성이 정말 지옥(hell) 오랜 상태로 이렇게 짜여왔기 때문에, 수정도 어렵다.

      (출처 : [https://preiner.medium.com/callback지옥에-promise-적용하기-d02272ecbabe](https://preiner.medium.com/callback%EC%A7%80%EC%98%A5%EC%97%90-promise-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-d02272ecbabe))

      (출처 : https://preiner.medium.com/callback지옥에-promise-적용하기-d02272ecbabe)

  2. 동기 vs 비동기

    1. 동기와 비동기의 개념

      출처 : https://smallzoodevs-organization.gitbook.io/copy-of-javascript-study/day-05./1.

      출처 : https://smallzoodevs-organization.gitbook.io/copy-of-javascript-study/day-05./1.

      1. 동기 : synchronous
        1. 현재 실행중인 코드가 끝나야 다음 코드를 실행하는 방식!
        2. CPU의 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적 코드.
        3. 계산이 복잡해서 CPU가 계산하는 데에 오래 걸리는 코드 역시도 동기적 코드
          😎
      2. 비동기 : a + synchronous ⇒ async라고들 흔히 부름
        1. 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식

        2. setTimeout, addEventListner 등

        3. 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 모두 비동기적 코드

          (출처 : https://velog.io/@mrbartrns/til-16-asynchronous-of-js)

          (출처 : https://velog.io/@mrbartrns/til-16-asynchronous-of-js)

      3. 웹의 복잡도가 올라갈 수록 비동기적 코드의 비중이 늘어남 🥲
  3. 콜백지옥의 예시와 해결방안

    조금은 익숙한 setTimeout을 통해 콜백 지옥의 간단한 예시!

    // setTimeout 함수의 동작원리
    setTimeout(function(){
    	// 기본적으로 1000ms이 지나야 여기 로직이 실행:)
    	console.log('hi');
    }, 1000);

    아래 예시코드는 다음과 같은 문제가 있음

    • 들여쓰기 수준 📉
    • 값 전달 순서 : 아래 → 위
    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,
      "에스프레소"
    );

    첫 번째 해결방법은 [기명함수로 변환]하는 방법.

    물고 물리면서 결국 끝까지 수행!

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

    가독성 좋음 위에서 아래로 코드 흐름이 이어지니까. 근데, 한 번만 쓰고 말텐데, 이렇게 이름을 다 붙여야 하는건 좀 그렇지 않을까?

    아쉽지만 위 코드는 근본적인 해결책은 아님. 이런 경우 때문에 자바스크립트에서는 비동기적인 작업을 동기적으로(동기적인 것 처럼 보이도록) 처리해주는 장치를 계속해서 마련해주고 있음. Promise, Generator(ES6), async/await(ES7)같은 것.

    비동기 작업의 동기적 표현이 필요.

    <비동기 작업의 동기적 표현(1) - Promise(1)>

    💡 **Promise에 대해**

    사실, Promise를 지금 완벽히 이해하기는 정말 어려운 일.

    Promise는 비동기 처리에 대해, 처리가 끝나면 알려달라는 ‘약속’

    • new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 바로 실행.
    • 그 내부의 resolve(또는 reject) 함수를 호출하는 구문이 있을 경우 resolve(또는 reject) 둘 중 하나가 실행되기 전까지는 다음(then), 오류(catch)로 넘어가지 않음.
    • 따라서, 비동기작업이 완료될 때 비로소 resolve, reject 호출.

    이 방법으로 비동기 -> 동기적 표현을 구현할 수 있음 🙂

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

    <비동기 작업의 동기적 표현(2) - Promise(2)>

    직전 예제의 반복부분을 함수화 한 코드 trigger를 걸어주기 위해 클로저 개념이 나왔지만, 여기서는 skip 하고, 다음 chapter에서 다루게 됨!

    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('카페라떼'));

    <비동기 작업의 동기적 표현(3) - Generator>

    💡 **이터러블 객체(Iterable)**

    제너레이터 문법이 등장해요. 생소하시죠?

    *가 붙은 함수가 제너레이터 함수입니다. 제너레이터 함수는 실행하면, Iterator 객체가 반환(next()를 가지고 있음)

    iterator 은 객체는 next 메서드로 순환 할 수 있는 객체. next 메서드 호출 시, Generator 함수 내부에서 가장 먼저 등장하는 yield에서 stop 이후 다시 next 메서드를 호출하면 멈췄던 부분 -> 그 다음의 yield까지 실행 후 stop

    즉, 비동기 작업이 완료되는 시점마다 next 메서드를 호출해주면 Generator 함수 내부소스가 위 -> 아래 순차적으로 진행 😊

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

    <비동기 작업의 동기적 표현(4) - Promise + Async/await>

    ES2017에서 새롭게 추가된 async/await 문을 이용했어요. 비동기 작업을 수행코자 하는 함수 앞에 async 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await를 붙여주면 됨.

    Promise ~ then과 동일한 효과를 얻을 수 있음

profile
𝙸 𝚊𝚖 𝚊 𝚌𝚞𝚛𝚒𝚘𝚞𝚜 𝚍𝚎𝚟𝚎𝚕𝚘𝚙𝚎𝚛 𝚠𝚑𝚘 𝚎𝚗𝚓𝚘𝚢𝚜 𝚍𝚎𝚏𝚒𝚗𝚒𝚗𝚐 𝚊 𝚙𝚛𝚘𝚋𝚕𝚎𝚖. 🇰🇷👩🏻‍💻

0개의 댓글