개발 공부한 것들/SeSSAC 웹 풀스텍 과정 회고록

[SeSSAC] 웹 풀스택 영등포 5기 과정_js동기처리, form기초

geograp 2023. 8. 15. 01:35

안녕하세요

오늘은 js의 비동기처리를 어떻게 동기처리로 바꾸는 지와 form의 기초를 배우는 시간이었습니다.

개인적으로 언어를 효율에 맞게 비동기방식으로 만들어 놓고 힘들게 동기를 찾는지 ㅠㅠ

원망스러울 따름이었지만 아무래도 필요한 기능인 것 같아,,,,, 열심히 들으려고 노력하였습니다.

요즘 잠을 많이 자는데도 피곤한건 왜 때문일까요?!?!?!?

 

먼저, 지난번 포스팅에 기재하였듯이 우리의 JS는 비동기처리 방식을 가진 언어입니다.

여기에 이제,,,node.js가 합세해서 JS의 비동기처리 언어 특성을 확장하고 강조하여

js+ node.js = 비동기 그 잡채인거죠,

 

이 덕분에 효율적이기는 하나, 로직에서 순서대로 꼭 처리해야할 경우

특별하게 순서를 강제로 지정해주어야 합니다.

 

여기에 두 가지 방식을 오늘 배웠습니다.

 

1. callback 함수를 이용해 비동기 처리

콜백 함수는 매개변수로 함수를 받고 그 함수가 다른 함수의 작업이 완료되었을 때 호출되는 함수입니다.

그러니까 콜백함수가 불리기 전까지 실행하지 않고 있다가 콜백함수가 실행되었을 때 실행한다는 말이죠

개발자가 함수의 순서를 직접 정할 수 있다는 것입니다.

function goMart() {
    console.log('마트에 가서 어떤 음료를 살지 고민한다.');
}

function pickDrink(callback) {
    setTimeout(function () {
        console.log('고민끝!!!');
        product = '제로콜라';
        price = 2000;
        callback(product, price); // *매개변수로 넘긴 콜백함수 실행
    }, 3000);
}

goMart();
pickDrink(function (product, price) {
    console.log(`상품명 : ${product}, 가격: ${price}`);
});

pickDrink() 함수 내에 매개변수로 callback함수를 받고 있습니다. (함수 이름은 당연히 변수입니다.)

여기서 6번째 줄의 콜백함수가 없었다면,

마지막 문장에서 찍히는 console.log에는 상품명과 가격이 undefined로 나오게 됩니다. 

그 이유는 (비동기적 처리 때문에) 아직 product와 price에 값이 대입되기 전에  

먼저 출력하는 간단한 함수를 실행해버리기 때문입니다.

 

그런데 값이 넣어지고 나서 상품명과 가격이 console에 찍혀야하잖아요?

그래서 pickDrink 에 매개변수로 함수를 넣어주고

이 콜백함수 위치를 product와 price의 값이 대입되고 나서 위치시켜주면

매개변수로 넣어준 함수가 순서에 따라 콜백함수가 불렸을 때 실행되어 잘 찍히게 됩니다.

이번에는 고민끝!! 이라는 문구도 함수 차례에 맞게 먼저 출력되고

상품명과 가격도 제대로 대입되어서 출력되는 것을 볼 수 있습니다 :)

 

이 콜백 함수가 다 좋은데, 지옥에 빠질 수가 있습니다.

콜백 함수를 끝없이 지옥처럼 불러야하는 경우가 생긴다 말인거죠.

(이를 콜백 지옥이라 합니다)

콜백 지옥 예시

그래서 이를 해결하고자 나온 다른 방법이 있습니다.


2. Promise 사용

 일단 이 아이는 이름값을 하는 아이입니다.

Promise = 약속한다는 거죠, 뭘 약속한다는 거죠? 

어떠한 결과값을 반드시 약속한 대로 반환한다는 것이죠

보통, 함수에서 에러가 나면 컴파일러가 에러를 내뱉거나 디버깅으로 찾아야하는데,

이 함수는 성공하든 실패하던 무조건 어떠한 반환값을 내놓는다는 거죠!

 

이 함수는 생성하는 부분과 사용하는 부분이 따로 필요합니다.

- 생성하는 코드

function promise1(flag) {
    return new Promise(function (resolve, reject) {
      if (flag) {
        resolve(`성공! 이 때 flag 값은 ${flag}!`);
      } else {
        reject(`실패! 이 때 flag 값은 ${flag}!`);
      }
    });
  }

Promise는 객체형식이라서 new를 이용해 해로 생성해주고, 매개변수로 함수를 받습니다.

이 때 함수는 정석이든 화살표 방식이든 상관없으나 resolve, reject를 꼭 인자로 받습니다.

그리고 상위의 코드는 flag가 true면 if문을 타고 resolve를 통해 결과값을 내고,

flag가 false면 else문을 타고 reject를 통해 결과값을 냅니다.

    resolve = 성공값
    reject = 실패값

그리고 여기까지는 단순히 promise객체를 만든 것일 뿐입니다.

진짜 사용은 따로 해줘야합니다.

promise1(5 > 3)
	.then(function(result) {
    console.log(result);
})
	.catch(function(error) {
	console.log(error);
});

사용하려면 당연히 함수를 불러주고, then과 catch라는 후위메서드를 사용하여

어떻게 사용할지 선택을 하게 됩니다.

then = 성공값
catch = 실패값

위의 함수에서는5 > 3이라는 조건식이 true이므로 then에 걸리게 되고

new Promise인 Promise1함수의 resolve부분으로 갑니다.

그럼 resolve 결과 값으로 문장이 있죠? 

그 문장을 result로 받아서 consol.log에 찍히는 과정입니다. 

 

만약 Promise1의 조건식이 5 < 3이라서 false로 인자를 받게 된다면

catch에 걸리게 되고 reject로 가서 reject에 있는 결과값인 문장을 받아서 error 변수에 넣겠죠!

 

이렇게 보면 그냥 callback함수 쪽이 더 편하다고 생각할 수도 있는데,

하기 실습 예제를 본다면 그렇지 않습니다.


이 예제는 1초마다 배경색이 red -> orange -> yellow -> green -> blue로 바뀌는 예제입니다.

먼저, callback함수를 사용하여 구현하였습니다.

    <script>
        setTimeout(function(callback){
			document.body.style.background = 'red';
			
			setTimeout(function(callback){
				document.body.style.background = 'orange';
				
				setTimeout(function(callback){	
					document.body.style.background = 'yellow';
					
					setTimeout(function(callback){	
					document.body.style.background = 'green';
						
						setTimeout(function(callback){
						document.body.style.background = 'blue';
						}, 1000);
					}, 1000);
				}, 1000);
			}, 1000);
       	}, 1000);
    </script>

보면 아까 본 그림과 비슷하게 콜백함수에 의해 들여쓰기가 굉장히 많이 되어있으며,

가독성도 떨어지고, 함수의 괄호 위치와 지역성 구분이 굉장히 떨어지는 상태입니다.

 

다음은 똑같은 예제를 promise를 활용하여 구현한 것입니다.

    <script>
        function bgColorChange (color) {
            return new Promise(function(resolve, reject){ //promise구현부
                setTimeout(function (){
                    let body = document.querySelector('body');
                    body.style.backgroundColor = color;
                    resolve();
                }, 1000);
            });
        }
        bgColorChange('red') // promise 사용부
        .then(function() {
            return bgColorChange('orange')})
        .then(function() {
            return bgColorChange('yellow')})
        .then(function() {
            return bgColorChange('green')})
        .then(function() {
            return bgColorChange('blue')})
    </script>

함수의 목적도 더 분명해보이고, 한눈에 봐도 가독성이 굉장히 좋아졌습니다.


그 다음 배웠던 것은, 미들웨어입니다. 

 

3. body-parser

요즘 node.js에는 body-parser가 설치되어있어 사용 시에 따로 설치하지 않아도 됩니다.

이 body-parser는 HTTP의 본문(body)를 파싱하여 사용 가능한 데이터 형태로 변환해주는 미들웨어입니다.

이 미들웨어는 use()함수와 같이 쓰여 요청(req)와 응답(res) 중간에서 여러 작업을 할 수 있습니다.

//req.body 객체를 해석할 수 있도록 body-parser 미들웨어 등록
app.use(express.urlencoded({ extended: true})); // post 요청으로 들어오는 모든 형식의 데이터를 파싱
app.use(express.json()); // json 형식으로 데이터를 주고 받겠다.

 사실 어떤 개념인지 한번에 확 와닿지는 않으나 이러한 use()함수와 미들웨어 등록을 통해 

 

우리가 데이터를 클라이언드와 서버가 주고 받을 때 중간에서 데이터를 잘 주고 받을 수 있도록

데이터를 가공하고 변형하여 약속된 형태로 조정하고

그에 필요한 처리를 수행하는 역할을 한다고 생각하면 될 것 같습니다.

 

이 미들웨어를 사용하여 클라이언트와 서버가 메세지를 전달받을 수 있는 메서드는 두 가지 였습니다.

 

4. get() 

이 get() 함수는 req.query에 사용자가 입력한 정보를 담아서 서버로 전송됩니다.

app.get('/getForm', (req, res) => {
    res.render('result', {
        title: 'GET 요청',
        userInfo: req.query,
    });
});

위의 함수는 get()을 썼는데,

/getForm이라는 경로가 추가가 된다면 render를 통해 result.ejs페이지를 불러와서 화면에 띄우고

userInfo라는 변수에 req.query(클라이언트가 입력한 정보들)을 담아서 서버에 보내는 것입니다. 

 

그럼 하기와 같이 req.query의 내용을 활용할 수 있습니다.

<body>
    <h1><%= title%> 폼 결과 확인하기</h1>
    <div>
        <span><%= userInfo.id%></span>님 환영합니다!
    </div>
    <div>비밀번호는<span><%= userInfo.pw%></span>이군요?</div>
</body>

ejs를 통해 userInfo 내의 id값에 접근하여 이렇게 span태그로 화면에 띄울 수 있습니다.

 

5. post()

이 post()함수는 req.body에 사용자가 입력한 정보를 담아서 서버로 전송됩니다.

app.post('/postForm', (req, res) => {
    res.render('result', {userInfo: req.body});
});

이 코드는 post()를 통해 경로에 /postForm이 추가가 된다면 render를 통해 result.ejs를 불러오고

userInfo라는 매개변수에 req.body(클라이언트가 입력한 정보들)을 받아서 서버로 보내게 됩니다.

 

그럼 하기와 같이 이 req.body를 활용할 수가 있습니다

 

<body>
    <h1><%= title%> 폼 결과 확인하기</h1>
    <div>
        <span><%= userInfo.id%></span>님 환영합니다!
    </div>
    <div>비밀번호는<span><%= userInfo.pw%></span>이군요?</div>
</body>

get()과 같은 코드입니다. 

어쨋든 get + req.query로 보내던 post + req.body로 보내던 둘 다 매개변수에 객체로 담아서 보냈으니

ejs에는 영향을 끼치지 않습니다.

 

그러나 여기에는 차이점이 존재합니다.

 

get + req.query 조합에는 파라미터에 정보가 담기므로 보안에 취약합니다.

그리고 이 URL에 정보가 담기기 때문에 URL의 길이 한정으로만 정보가 담길 수 있습니다.

그러나 브라우저에 캐시할 수 있어 이전 결과 요청을 재사용할 수 있다는 장점이 있습니다.

post + req.body조합에는 파라미터에 정보가 담기지 않아서 보안에 강합니다.

그리고 body를 통해 전송되므로 많은 양의 정보가 담길 수 있습니다.

그러나 브라우저에 캐시할 수 없어 이전 결과 요청을 재사용할 수 없다는 단점이 있습니다.

보통 데이터를 조회하거나 검색할 경우에는 get()을 많이 사용하며 (ex, 내 아이디 검색)

데이터를 수정하고 생성하는 경우에는 post()를 많이 사용한다고 합니다. (ex, 회원가입)

 

하여 제가 get() 메서드를 사용하여 실습한 것을 아래에 올리고 포스팅을 마치도록 하겠습니다 :)


js 코드

const express = require('express')
const app = express();
const PORT = 8080;

app.set('view engine', 'ejs');
app.set('/views', 'views');

app.use(express.urlencoded({ extended: true}));
app.use(express.json())

app.get('/', (req, res) => {
    res.render('index2');
});

app.get('/register', (req, res) => {
    res.render('register', {userInfo: req.query});
});

app.listen(PORT, () => {
    console.log(`${PORT} is open!`);
});

index2.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>실습_get으로 정보 받기</title>
</head>
<body>
    <form action="/register" method="get">
    <span>이름</span>
    <input type="text" name="name">
    <fieldset action="/" method="get">
        <legend>성별</legend>
        <input type="radio" name="gender" value="female">
        <label for="female">여자</label>
        <input type="radio" name="gender" value="male">
        <label for="male">남자</label>
    </fieldset>
    <fieldset action="/" method="get">
        <legend>생년월일</legend>
        <select name="birth_year" id="">
            <% for (let i = 1990; i<= 2023; i++) {%>
            <option value="<%= i%>"><%= i%></option>
            <%}%>
            </select>
            <span>년</span>
        <select name="month" id="">
            <% for (let i = 1; i<= 12; i++) {%>
                <option value="<%= i%>"><%= i%></option>
            <%}%>
        </select>         
        <span>월</span>
        <select name="day" id="">
            <% for (let i = 1; i<= 31; i++) {%>
                <option value="<%= i%>"><%= i%></option>
            <%}%>
        </select> 
        <span>일</span>
    </fieldset>
    <fieldset action="/" method="get">
        <legend>관심사</legend>
        <input type="checkbox" name="interest" value="travel">
            <label for="traval">여행</label>
        <input type="checkbox" name="interest" value="fashion">
            <label for="fashion">패션</label>
        <input type="checkbox"  name="interest" value="food">
            <label for="food">음식</label>
    </fieldset>
    <fieldset>
        <legend>선택</legend>
        <span> 좋아하는 색상</span>
        <input type="color" name="fav_color" value="#111">
    </fieldset>
    <button>회원가입</button>
    </form>
</body>
</html>

register.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>회원가입</title>
</head>
<body>
    <p>고객님의 이름은 <%= userInfo.name%>이군요!</p>
    <p>고객님의 셩별은 <%= userInfo.gender%>이군요!</p>
    <p>고객님의 생년월일은 <%= userInfo.birth_year%>년 <%= userInfo.birth_month%>월 <%= userInfo.birth_day%>일이군요!</p>
    <p>고객님의 취미는 <%= userInfo.interest%>이군요!</p>
</body>
</html>