국비교육(22-23)

33일차(4)/javascript : Promise

서리/Seori 2022. 11. 24. 00:45

33일차(4)/javascript : Promise

 

- javascript Promise 객체 사용법, 예제

 

- javascript는 비동기 방식으로 처리되고, 스레드가 하나뿐이다.

 그래서 순서대로 어떤 작업을 하려고 할 때 좀더 복잡한 절차가 필요할 수 있다.

 

<test01.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test01.jsp</title>
</head>
<body>
	<h1>비동기 테스트</h1>
	<script>
	
		setTimeout(function(){
			console.log("5초가 경과됐네?");
		}, 5000);
		
		//가상의 주어진 작업을 처리 하는 함수 (주어진 작업을 처리하는데  random 한 시간이 걸린다고 가정하자)
		function work(job, callback){
			
			//0 ~ 5000 사이의 랜덤한 숫자 얻어내기
			let ranTime=Math.random()*5000;
			
			// 첫번째 인자로 전달된 함수를 random 한 이후에 호출하기 
			setTimeout(function(){
				console.log(job+" 을(를) 수행 했습니다.");
				//callback 안에는 function type이 들어있다는 가정하에서 해당 함수를 호출한다.
				callback();
			}, ranTime);
			
		}
		
		work("냉장고 문을 연다", function(){			
			work("코끼리를 넣는다", function(){
				work("냉장고 문을 닫는다", function(){
					
				});
			});
		});
		
		
		/*
		work("달리기", function(){
			work("쉬기", function(){
				work("잠자기", function(){
					
				});
			});
		});
		*/

		//1. Promise 객체를 생성해서 참조값을 p1 에 담기
		let p1=new Promise(function(resolve){
			/*
				함수의 인자로 전달되는 resolve는 함수이다.
				resolve 는 작업을 완료 했을때 호출 해야하는 함수
				resolve 함수가 호출되면 .then() 안에 있는 함수가 자동 호출된다.
			*/
			resolve();
		});
		
		p1.then(function(){
			console.log("then 안에 있는 함수가 호출되었습니다.");
		});
		
	</script>
</body>
</html>

 

setTimeout(function(){
	console.log("5초가 경과됐네?");
}, 5000);

- 어떤 작업이 5초 후 처리되도록 했을 경우,

 

- 5초 뒤에 호출되는 함수이다.

 


 

function work(job, callback){}

 

- work는 문자열, 함수를 전달하는 함수이다.

- 문자열로 전달한 작업이 끝나면 이 함수를 호출하는 구조

 

- 이 함수를 호출하게 되면 임의의 시간(ranTime) 이후에 이 작업이 끝난다.

- work 함수를 호출하면서 함수를 하나 전달하는 것이다.

 

 

work("냉장고 문을 연다");
work("코끼리를 넣는다");
work("냉장고 문을 닫는다");

 

- 위 작업은 순서대로 일어나야 한다.

- 하지만 실행해보면 순서가 일정하지않다. 작업이 수행되는 데 걸리는 시간이 랜덤이기 때문에 무엇이 먼저 끝날지 알 수 없다.

 

- javascript는 비동기 방식으로 처리된다.

 


 

ex) 두 가지 물건을 사용한다고 하자.

1) 커피컵 : 주문하면 일정 시간 후에 받을 수 있다.

2) 커피머신 : 버튼을 누르면 나온다.

→ 커피컵을 먼저 주문해도, 반드시 커피컵이 먼저 나오리라는 보장은 없다.

 

- 이 문제의 해결방법은?

1. 커피컵을 주문하고, 컵이 나올때까지 기다린 다음 주문한 컵이 나오면 

  커피머신의 버튼을 눌러서 컵을 받치고 커피가 나오길 기다린다.

→ 문제가 발생하지는 않지만, 컵을 기다리는 동안은 다른 일을 하지 못한다. javascript는 이렇게 일하면 안된다.

 

- javascript는 일하는 사람이 한명이다! 커피컵이 나올때까지 그 시간동안 다른 작업을 하지 못한다.

- javascript는 single thread 구조이다. 

 

- java의 경우, 새 thread를 사용해서 동시에 여러 일을 진행할 수 있다. 그 사이에 메인 thread는 다른 일을 처리할 수 있다.

→ 이렇게 처리하는 것을 동기 처리 라고 부른다.

 

2. 커피컵을 주문하고, 컵이 나오면 저를 불러주세요~ 한다. (그동안 저는 다른 일을 할게 있어요)

컵이 나왔다고 부르면 컵을 받아서 커피머신에 컵을 받치고 버튼을 누른 후,

다른 데로 간다(커피가 다 나오면 저를 불러주세요~)

→ 이렇게 처리하는 것을 비동기 처리 라고 부른다.

 

- javascript는 비동기 처리: 함수를 하나 두고 갈 테니 함수를 호출해주세요~ 라고 생각하면 된다.

  작업하고, 작업이 끝나면 이 함수 호출해줘~ 라는 함수가 callback함수.

 

- 비동기 처리의 주의사항 : 순서가 중요한 처리를 할때, javascript의 구조상 코드가 복잡해진다.

- javascript에서 순서가 중요한 처리를 할 때는 promise라는 객체를 사용해 어려움을 보완한다.

 


 

- javascript에서는 한 번에 여러 일을 처리하면 순서를 보장할 수가 없다.

 

- 다음 함수는 냉장고 문을 열고 나서 호출되도록 한다.

- work라는 함수를 호출하면서 전달한 함수는 저 일이 끝나면 알아서 호출된다.

→ 이런 함수를 callback 함수라고 한다.

 

- 이 함수 안에서 냉장고 문을 연 다음에 할 작업을 하면 된다.

- 저 function 안에 함수를 넣어주면 순서가 바뀔 염려는 없다!

 

- javascript는 이렇게 함수 안에서 함수가 처리되는 모양을 갖는다.

 

 

- 비동기 처리되는 작업을 순서대로 작업하고 싶으면 이렇게 하면 된다.

- 이것을 javascript 콜백 지옥이라고 부른다....

 

.then(function(){})

.then(function(){})

.then(function(){});

 

- 이를 개선하기 위해 promise가 나온 것!

- 왼쪽과 같은 작업을 오른쪽과 같은 모양으로 바꿀 수 있다. 1,2,3번 함수를 직렬화해서 나열한 것이다.

 

- promise는 javascript 의 기본 클래스 중 하나이다.(함수 형태) 클래스라고 생각해도 무방하다.

- new Promise() 하고 그 객체 안에 있는 것들을 어떻게 사용하는지를 익히면 된다.

 

 

- promise객체를 생성해서 .then함수를 호출하면서 함수(resolve)를 하나 전달하면

 resolve가 콜되면서 알아서 then안에 들어있는 함수가 호출된다.

 


 

<test02>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test02.jsp</title>
</head>
<body>
	<h1>Promise 테스트</h1>
	<script>
		//1. Promise 객체를 생성해서 참조값을 p1 에 담기
		let p1=new Promise(function(resolve, reject){
			/*
				함수의 인자로 전달되는 resolve, reject는 함수이다.
				resolve 는 작업을 완료 했을때 호출 해야하는 함수
				reject 는 작업이 실패 했을때 호출 해야하는 함수 
				resolve 함수가 호출되면 .then() 안에 있는 함수가 자동 호출된다.
				reject 함수가 호출되면 .catch() 안에 있는 함수가 자동 호출된다.
			*/
			resolve();
			//reject();
			console.log("reject 함수를 호출 했습니다.");
		}); 
		//2. p1 Promise 가 resolve 되었을때 호출되는 함수 등록
		p1.then(function(){
			console.log("then 안에 있는 함수가 호출되었습니다.");
		});
		
		//3. p1 Promise 가 reject 되었을때 호출되는 함수 등록
		p1.catch(function(){
			console.log("catch 안에 있는 함수가 호출되었습니다.")
		});	
	</script>
</body>
</html>

 

- 함수의 인자로 전달되는 resolve, reject는 함수이다.
- resolve작업을 완료했을때 호출해야하는 함수
  reject작업이 실패했을때 호출해야하는 함수
- resolve 함수가 호출되면 .then() 안에 있는 함수가 자동 호출된다.
  reject 함수가 호출되면 .catch() 안에 있는 함수가 자동 호출된다.

 

- 작업이 정상적으로 완료되었을 때 resolve를 호출하면 .then이 실행됨

- 원하는 작업이 제대로 되지 않았을 때 reject가 호출되면서 .catch가 실행됨

 

let p1=new Promise(function(resolve, reject){ });

- 작업이 제대로 되면 resolve 호출, 실패하면 reject를 호출하도록 약속된 것!

(매개변수명은 자유롭게 지을 수 있다)

 


 

<test03>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test03.jsp</title>
</head>
<body>
	<h1>Promise 테스트</h1>
	<script>
		//아래와 같은 형식으로 작성할수도 있다.	
		new Promise(function(resolve, reject){
			resolve();
			console.log("resolve 호출됨");
		})
		.then(function(){
			console.log("then() 안에 있는 함수 호출됨");
		})
		.catch(function(){
			console.log("catch() 안에 있는 함수 호출됨");
		});
	</script>
</body>
</html>

 

- 변수 p1에 담아놓고 쓸 수도 있지만, 좀더 편하게 쓰려면 .then / .catch 로 요약해서 쓸 수 있다.

- 객체를 생성하고나서 바로 . 찍어서 연속으로 쓸 수 있다.

 


 

<test04>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test04.jsp</title>
</head>
<body>
	<h1>Promise 테스트</h1>
	<script>
		//아래와 같은 형식으로 작성할수도 있다.	
		new Promise(function(resolve, reject){
			//비동기 작업의 결과 데이터가 있다면
			let data={num:1, name:"바나나"};
			//resolve() 함수를 호출하면서 전달할수도 있다.
			resolve(data);
			console.log("resolve 호출됨");
		})
		.then(function(result){
			//resolve() 함수에 전달된 값이 then() 안에 있는 함수의 인자로 전달된다.
			console.log(result);
			console.log("then() 안에 있는 함수 호출됨");
		})
		.catch(function(){
			console.log("catch() 안에 있는 함수 호출됨");
		});
	</script>
</body>
</html>

 

- resolve를 호출하면서 데이터를 전달하기

- resolve("어떤 값")을 .then(function("어떤 값"){ }); 안에서 인자로 받아서 사용한다.

 


 

<test05>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test05.jsp</title>
</head>
<body>
	<script>
		new Promise(function(resolve){
			resolve();
		})
		.then(function(){
			console.log("첫번째 then() 호출됨");
			return new Promise(function(resolve){
				resolve();
			});
		})
		.then(function(){
			console.log("두번째 then() 호출됨");
		});
		
	</script>
</body>
</html>

 

- 연두색: 첫번째 promise / 분홍색: 두번째 promise

- 리턴값 부분에 또 다른 promise를 생성해서 리턴하면 then을 연결할 수 있다.

- 작업에 성공하면 resolve를 호출. 실패하면 reject를 호출하기로 약속한 것!

 


 

<test06>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test06.jsp</title>
</head>
<body>
	<script>
		//가상의 주어진 작업을 처리 하는 함수 (주어진 작업을 처리하는데  random 한 시간이 걸린다고 가정하자)
		function work(job, callback){
			
			//0 ~ 5000 사이의 랜덤한 숫자 얻어내기
			let ranTime=Math.random()*5000;
			
			// 첫번째 인자로 전달된 함수를 random 한 이후에 호출하기 
			setTimeout(function(){
				console.log(job+" 을(를) 수행 했습니다.");
				callback();
			}, ranTime);	
		}
		
		new Promise(function(resolve){
			//첫번째 작업을 한다.
			work("달리기", resolve);
		})
		.then(function(){
			return new Promise(function(resolve){
				work("쉬기", resolve);
			});
		})
		.then(function(){
			return new Promise(function(resolve){
				work("잠자기", resolve);
			});
		})
		.then(function(){
			console.log("모든 작업을 순서대로 잘 처리 했습니다.");
		});
	</script>
</body>
</html>

 

- 어떤 작업을 순서대로 처리할 때 .then을 사용해서 순차적으로 처리되도록 한다.

 

- 1번은 에러처리가 났을 때 처리하기가 힘들다..

- 그래도 2번은 catch가 있어서 예외사항이 발생했을 때 처리가 수월하다.

 

- 사실 new Promise 할 일은 별로 없다.

  보통 javascript의 라이브러리가 리턴해주는 promise를 사용하는 입장이다.

  .then 부분을 주로 작성한다. 

 

- 어떤 라이브러리를 봤을 때 이렇다면... 저 빨간 박스가 Promise 객체라고 생각하면 된다.

 


 

<test07>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test07.jsp</title>
</head>
<body>
	<script>
		//가상의 주어진 작업을 처리 하는 함수 (주어진 작업을 처리하는데  random 한 시간이 걸린다고 가정하자)
		function work(job, callback){
			
			//0 ~ 5000 사이의 랜덤한 숫자 얻어내기
			let ranTime=Math.random()*5000;
			
			// 첫번째 인자로 전달된 함수를 random 한 이후에 호출하기 
			setTimeout(function(){
				console.log(job+" 을(를) 수행 했습니다.");
				callback();
			}, ranTime);	
		}
		
		//어떤 작업을 시작하고 Promise 객체를 리턴하는 함수 
		function startWork(job){
			//Promise 객체를 생성해서 
			let p=new Promise(function(resolve){
				work(job, resolve);
			});
			//리턴해주기 
			return p;
		}
		
		
		startWork("달리기")
		.then(function(){
			return startWork("쉬기");
		})
		.then(function(){
			return startWork("잠자기");
		})
		.then(function(){
			console.log("모든 작업을 순서대로 마쳤습니다.");
		});
		
		/*
		work("달리기", function(){
			work("쉬기", function(){
				work("잠자기", function(){
					console.log("모든 작업을 순서대로 마쳤습니다.");
				});
			});
		});
		*/
	</script>
</body>
</html>

 

- 특별한 Promise 를 리턴하는 함수를 누군가 만들어놔서 그것을 사용하는 입장인 것!

- 주로 .then의 내용을 작성하게 된다.

 

- then 안의 함수는 즉시 호출된다. / resolve는 일정 시간 이후에 호출된다.(앞의 일이 처리되고 나서)

- promise → resolve → then → promise → resolve → then ... 이런 구조!

 

 

 

- 이런 코드를 보면 fetch라는 함수가 리턴해주는 무언가는 promise 객체라는 것을 알 수 있다.

 

- 아래 달린 .then을 보면 Promise 함수인 것을 알 수 있다.

- 주로 누군가가, 무언가가 리턴해주는 promise를 사용한다. 모양을 보고 promise 라는 것을 알면 된다.

 


 

<test08>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test08.jsp</title>
</head>
<body>
	<script>
		/*
			Promise 는 
			언제 끝날지 모르는 불확실한 비동기 작업을
			콜백 지옥에 빠지지 않고
			순서대로 수행하고자 할때 사용합니다.
			- 특히 작업이 실패 되었을때 처리가 훨씬 수월 합니다.
		*/
		function openDoor(){
			return new Promise(function(resolve){
				setTimeout(function(){
					console.log("1초를 소모하면서 문을 열었습니다.");
					resolve();
				}, 1000);
			});
		}
		function putElephant(){
			return new Promise(function(resolve){
				setTimeout(function(){
					console.log("3초를 소모하면서 코끼리를 집어 넣었습니다.");
					resolve();
				}, 3000);
			});
		}
		function closeDoor(){
			return new Promise(function(resolve){
				setTimeout(function(){
					console.log("2초를 소모하면서 문을 닫았습니다.");
					resolve();
				}, 2000);
			});
		}
		
		
		openDoor()
		.then(function(){
			return putElephant();
		})
		.then(function(){
			return closeDoor();
		})
		.then(function(){
			console.log("코끼리를 성공적으로 냉장고에 넣었습니다.");
		});
		
	</script>
</body>
</html>

 

- 밑줄이 그어진 함수 전부 promise 타입 객체이다.

- promise타입이 리턴되어야만 then이 달릴 수 있다.

 

 

- 안에서 리턴되는 것도 모두 promise 타입이다. 모양을 익히기!


 

<test09>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test09.jsp</title>
</head>
<body>
	<h1>fetch() 함수 테스트</h1>
	<button id="myBtn">눌러보셈</button>
	<a href="${pageContext.request.contextPath }/index.jsp">눌러보셈</a>
	<script>
		document.querySelector("#myBtn").addEventListener("click", function(){
			//페이지 전환 없이 index.jsp 페이지로 요청하기 
			fetch("${pageContext.request.contextPath }/index.jsp")
			.then(function(response){
				return response.text();
			})
			.then(function(data){
				//index.jsp 페이지가 응답하는 문자열이 함수의 인자로 전달된다. 
				console.log(data);
			});	
		});
	
	</script>
</body>
</html>

- 추출한 문자열을 javascript로 받을 수 있다.

 

- promise 기능을 사용해 페이지 전환 없이 프로필 사진을 업로드하는 기능을 구현할 수 있다.