Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

heyday2024 님의 블로그

[4주차 JS 문법(2)] 콜백지옥과 비동기 제어 본문

프론트엔드 부트캠프

[4주차 JS 문법(2)] 콜백지옥과 비동기 제어

heyday2024 2024. 10. 22. 18:18

1. 콜백지옥

콜백함수를 익명함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 헬인 경우!

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

 

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

----> 가독성이 정말 지옥(hell)임. 


2. 동기 vs 비동기

 

출처 : 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)ALT

----> 웹의 복잡도가 올라갈수록 비동기적 코드의 비중이 늘어남


 

<콜백 지옥 예시>

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

가독성이 너무 떨어짐...

 

<콜백지옥 해결방안>

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

- 가독성은 좋은데, 한번 쓰고 말 코드에 이름을 하나씩 다 붙여쓰는 것은 너무 비효율적임

- 그리고, 각각을 할당하기 위한 메모리/리소스도 너무 낭비되는 느낌...


 

===> 위 코드는 근본적인 해결책은 아닌 것 같아요. 이런 경우 때문에 자바스크립트에서는 비동기적인 작업을 동기적으로(동기적인 것 처럼 보이도록) 처리해주는 장치를 계속해서 마련해주고 있어요.

Promise, Generator(ES6), async/await(ES7)같은 것들이죠.

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


(2) 비동기 작업의 동기적 표현

비동기 작업은 순서를 보장하지 않음.

--> 그러나 순서대로 처리되어야하는 비동기 작업들도 있을 수 있음

(ex) 위의 예시처럼, 음료를 순서대로 처리하는 것....

 

===> 그래서 비동기 작업의 동기적 표현이 필요함!!!!

 

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

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

  • new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 바로 실행돼요.
  • 그 내부의 resolve(또는 reject) 함수를 호출하는 구문이 있을 경우 resolve(또는 reject) 둘 중 하나가 실행되기 전까지는 다음(then), 오류(catch)로 넘어가지 않아요. (처리 성공적으로 완료 : resolve, 실패: reject)
  • 따라서, 비동기작업이 완료될 때 비로소 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);
	});
});

- 앞의 promise가 resolve가 실행(작업 성공적으로 완료)되어야만 then으로 그 다음을 실행함.

- then으로 그 다음, 그 다음, 그 다음의 코드를 순차적으로 실행하게함(비동기적 작업은 동기적으로~~ chaining)

 

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

직전 예제의 반복부분(then으로 계속 처리했던 코드 내용/작업들이 같았음)을 함수화 한 코드.(refactoring)

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

- addCoffee('에스프레소')는 function(prevName)함수 부분임으로 이 함수를 실행시키기 위해 () 뒤에 붙여줌.

- 앞의 함수(작업)가 실행이완료되야만 then 부분이 실행됨(순차적으로)

- prevName이 있을때는 prevName도 다 출력, 없으면 name만. (삼항 연산자)

 

 

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

genertator는 반복할 수 있는 iterable 객체를 반환함.

- 이터러블 객체(Iterable)

 

function에 *가 붙은 함수가 제너레이터 함수입니다. 제너레이터 함수는 실행하면, 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();

-coffeeGenerator는 genertator 함수로 iterable 객체를 반환함(여기서는 coffeMaker)

- yield를 만나면 일단 멈춤, 그 뒤 함수가 끝날 때까지 기다려주고, 다시 next()메서드를 호출하면 중단했던 부분에서 실행 재개함.

 

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

ES2017에서 새롭게 추가된 async/await 문을 이용.

 

비동기 작업을 수행코자 하는 함수 앞에 async 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 **await**를 붙여주면 됨

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

 

//Promise
//예전에는 then(그러면~)을 썼지만, 이젠 async(비동기) /awiat(기다리다)를 써도 됨.

//coffeeMaker 함수에서 호출할 함수, 'addCoffee' 선언
//Promise 반환

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

//화살표 함수하면 그 앞에 async 붙여줌 (ex) asunc () =>{}
//async 붙여준 함수 내부 스코프에서 await을 만나, 해당 메서드가 작업이 끝날 때까지 기다림.


var coffeeMaker = async function () {
	var coffeeList = '';
	var _addCoffee = async function (name) {
		coffeeList += (coffeeList ? ', ' : '') + await addCoffee(name);
	};
    
    //promise를 반환하는 함수인 경우, await을 만나면 무조건 끝날 때까지 기다림.
	await _addCoffee('에스프레소');
	console.log(coffeeList);
	await _addCoffee('아메리카노');
	console.log(coffeeList);
	await _addCoffee('카페모카');
	console.log(coffeeList);
	await _addCoffee('카페라떼');
	console.log(coffeeList);
};
coffeeMaker();