
(이미지 출처 : https://preiner.medium.com/callback지옥에-promise-적용하기-d02272ecbabe)
콜백 함수를 익명 함수(매개변수로 전달..)로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 헬 수준인 경우를 말한다. ^~^
주로 이벤트 처리 및 서버 통신과 같은 비동기적 작업을 수행할 때 발생
문제점: 가독성 지옥(hell), 오랜 상태로 이렇게 짜여왔기 유지보수가 어렵다.
완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식setTimeout, addEventListner 등
복잡도가 올라갈 수록 비동기적 코드의 비중이 늘어난다!
// setTimeout 함수의 동작원리
setTimeout(function(){
// 기본적으로 1000ms이 지나야 여기 로직이 실행 된다
console.log('여기가 먼저 실행될까?');
}, 1000);
console.log('여기 봐주세요~');
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, '에스프레소');
이런 경우 때문에 자바스크립트에서는 비동기적인 작업을 동기적으로(동기적인 것 처럼 보이도록) 처리해주는 장치를 계속해서 마련해주고 있다.
PromiseGenerator(ES6)async/await(ES7)비동기 작업의 특징: 순서를 보장하지 않는다.
그렇기 때문에 언제 제어권이 다시 나에게 올지 모른다는 것이 비동기 작업의 특징이다.
setTimeout()은 몇 초 뒤인지 알잖아요?
그건 setTimeout입장이고, 제어권 넘겨준 코드 입장에선 모른다.
naver 날씨 api 정보 얻고, 그 날씨 정보를 바탕으로 kakao 지도 api 정보를 얻어 오고자 한다면?
일의 순서가 naver 먼저 선행되야 하고 다음에 kakao에 보내야 한다.
원래는 네이버 3초 걸리고 kakao는 8초 걸린다고 해보자.
그런데 네이버에 과부하 걸려서 시간 10초 이상으로 더 많이 걸려 버린다면 kakao한테 정보를 전달할 수가 없으므로 내부 서버 입장에서 대응 할 수 없게 되어 버린다.
이 처럼 서버 통신 처럼 비동기 함수 로직으로 가게 되면 이런 상황 발생할 수 있기 때문에,
비동기 작업을 동기적으로 표현하는 시도가 필요하다.
Promise는 비동기 처리에 대해, 처리가 끝나면 알려달라는 ‘약속’이다.
new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 바로 실행된다.resolve(성공) (또는 reject(실패)) 함수를 호출하는 구문이 있을 경우,다음(then), 오류(catch)로 넘어가지 않는다.resolve, reject를 호출한다.resolve > Promise.then 으로 넘어감rejcect > Promise.catch 로 넘어감이런 방법으로 비동기 -> 동기적 표현을 구현할 수 있다.
// Promise 만들기
// 즉시 실행되는 콜백 함수 넣기 <- 인자로 resolve로 넣자
new Promise(function (resolve) {
//0.5초 후에 커피 이름 찍기
setTimeout(function () {
var name = '에스프레소';
console.log(name);
resolve(name); // resolve() 실행되면
}, 500);
// then(다음 약속)으로 넘어간다
// 콜백함수 인자엔 resolve(name) name이 들어가는데
// 안 헷갈리게 prevName 이름으로 넘겨 주자
}).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('에스프레소') 는
//function ('에스프레소') {
// return new Promise(function (resolve) {
// setTimeout(function () {
// var newName = '에스프레소' ? `${'에스프레소'}, ${name}` : name;
// console.log('에스프레소');
// resolve('에스프레소');
// }, 500);
// });
// };
//니까
//addCoffee('에스프레소')() 라고 해줘야 리턴 문이 실행된다.
addCoffee('에스프레소')()
.then(addCoffee('아메리카노'))
.then(addCoffee('카페모카'))
.then(addCoffee('카페라떼'));
이터러블 객체(Iterable)
iterable: 반복될 수 있는, 반복할 수 있는 제너레이터 문법
*가 붙은 함수가 제너레이터 함수이다.
제너레이터 함수는 실행하면, Iterator 객체가 반환(next()를 가지고 있음)된다.
따라서 제너레이터는 반복할 수 있는 이터레이터 객체를 생성한다.
iterator 객체는 next 메서드로 순환 할 수 있는 객체이다.
next 메서드 호출 시, Generator 함수 내부에서 가장 먼저 등장하는 yield에서 stop 이후 다시 next 메서드를 호출하면 멈췄던 부분
-> 그 다음의 yield까지 실행 후 stop
yield: 양보하다, 미루다
순서를 기다리지 않는 비동기 작업을 양보하고 미루게 하면서 순서를 기다리게 만드는 역할
순차적으로 진행된다.비동기적 작업은 순서를 보장 받을 수 없기 때문에,
순서를 보장 받기 위해서 동기적으로 표현하도록 계속 노력하는 것!
잊지 말자!
// *가 붙은 함수가 제너레이터 함수
// 이 함수를 실행하면 => iterator 객체가 반환된다.
// 2. 제너레이터 함수 안에서 쓸 addCoffee 함수 선언
var addCoffee = function (prevName, name) {
setTimeout(function () {
coffeeMaker.next(prevName ? prevName + ', ' + name : name);
}, 500);
};
// 1. 제너레이터 함수인 coffeeGenerator 선언
var coffeeGenerator = function* () {
// Generator 함수 내부에서 yield 키워드로 순서 제어함
// 5-1. Generator 함수 내부에서 가장 먼저 등장하는 yield 키워드를 만나면 stop!
// 그 로직이 끝날 때 까지 기다린 후,
// 5-2. 이후 다시 next 메서드를 호출하면 멈췄던 부분 그 다음의 yield까지 실행 후 stop!
// 끝날때 까지 반복 ㅇㅇ! <- 스탑을 걸어준다! 는 느낌
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);
};
// 3. coffeeGenerator() -> 제너레이터 함수인 coffeeGenerator를 실행하면
// iterator 객체를 반환하여 갖게 된다.
var coffeeMaker = coffeeGenerator();
// 4. 따라서 coffeeMaker는 iterator 객체이고,
// iterator 객체는 next() 메서드로 순환할 수 있다.
// 5. coffeeMaker.next(); 로 next 메서드 호출 시
coffeeMaker.next();
ES2017에서 새롭게 추가된 async(비동기)/await(기다리다) 문을 이용하여 비동기 작업을 동기적으로 표현할 수 있다.
async를 붙이고,await를 붙여주면 된다.
// 1. coffeeMaker 함수에서 호출할
// Promise 리턴하는 addCoffee 함수를 선언
var addCoffee = function (name) {
return new Promise(function (resolve) {
setTimeout(function(){
resolve(name);
}, 500);
});
};
// 2. coffeeMaker 함수는 비동기 함수로 async 를 붙여주면,
var coffeeMaker = async function () {
var coffeeList = '';
var _addCoffee = async function (name) {
coffeeList += (coffeeList ? ', ' : '') + await addCoffee(name);
};
// 3. Promise를 반환하는 함수인 경우에는 (addCoffee),
// await 키워드를 만나면, 무조건 그 메서드 끝날 때 까지 기다린다.
// 기다리게 해줘라
await _addCoffee('에스프레소'); // 이 로직이 모두 실행될 때까지 기다림, 100초 걸리면
console.log(coffeeList); // 이 콘솔은 100초 뒤에 찍힘
await _addCoffee('아메리카노');
console.log(coffeeList);
await _addCoffee('카페모카');
console.log(coffeeList);
await _addCoffee('카페라떼');
console.log(coffeeList);
};
coffeeMaker();