프론트엔드 개발일지 #13-Javascript ( 콜백지옥과 비동기제어)

조아라·2024년 10월 13일
1
post-thumbnail

콜백 지옥

익명함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 헬 수준인 경우
주로 이벤트 처리 및 서버 통신과 같은 비동기적 작업을 수행 할 때 발생

동기 vs 비동기

  • 동기(synchronous) : 현재 실행중인 코드가 끝나야 다음 코드를 실행하는 방식

  • 비동기(a + synchronous ⇒ async) : 실행중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식. (setTimeout, addEventListner 등)
    별도의 요청, 실행대기, 보류 등과 관련된 코드는 모두 비동기적 코드이다.

💁‍♀️[ 콜백지옥의 예시와 해결방안 ]

일단 콜백 함수에 흔히 쓰이는 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,
  "에스프레소"
);

가독성도 좋지 못하고, 들여쓰기가 너무 심각하다. 이걸 해결하는 첫번째 방법은
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)이다.


  • 비동기 작업의 동기적 표현 (1) - 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);
	});
});

이 코드의 반복 부분을 함수화한 코드이다.

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('카페라떼'));
  • 비동기 작업의 동기적 표현 (2) - Generator

먼저 이터러블 객체(Iterable)의 뜻을 알아야 한다. 반복될 수 있는, 반복할 수 있는 이라는 뜻이다. 제너레이터 문법에 등장하낟. '*'가 붙은 함수가 제너레이터 함수이고, 제너레이터 함수를 실행하면 이터레이터 객체가 반환(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();
  • 비동기 작업의 동기적 표현(3) - Promise + Async/await
    ES2017에서 새롭게 추가된 async/await 문을 이용. 비동기 작업을 수행코자 하는 함수 앞에 async 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await를 붙여주면 된다.

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

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

profile
끄적 끄적 배운 걸 적습니다 / FRONT-END STUDY VELOG

0개의 댓글