비동기 함수의 동기화 <async await 문법>

alert("april");·2023년 9월 27일
0

React

목록 보기
11/17


출처 https://www.boardinfinity.com/blog/javascript-async-and-await-function/

비동기함수

fetch
axios.get
axios.post
axios...

pool.query

보통 데이터를 갖고올때 오래걸리는 애들
우리가 생각할때 불편한 애들
순서가 위-> 아래가 아니기때문에

async await 문법 javaScript

비동기 함수를 동기 함수처럼 사용하고 싶어

비동기 함수 앞에 await를 사용하면 동기함수로 해석한다
await를 썼으면 await를 감싸고있는 함수를 async함수라고
알려줘야 한다!!

가독성을 위해 메모를 코드 블럭 안에 넣어두었습니다.

    일반적인 함수
    async funtion 함수이름(){
        await
    }
    익명함수
    async function(){

    }

    화살표 함수
    async ()=>{

    }

ex1)
console.log('코드1');
axios.get('ddd').then(()=>{console.log('')}).catch(()=>{});
console.log('코드3');

ex2)
pool.query('SELECT * FROM tmp', (err, result, fields)=>{
    // 이 공간은 sql이 끝나고 동기적으로 실행되는 곳
    // err 에는 오류발생하면 err객체 안발생하면 null
    // result 에는 ? 정상적으로 끝나면 결과(배열, 객체)
        오류발생시 undefined
    // fields 에는 ? 정상적으로 끝나면 각 컬럼에 대한 정보
        오류발생시 혹은 SELECT 아닌경우 undefined
});
console.log('dd');

ex3)
axios.get('/users').then((res)=>{
    // get함수가 정상적으로 끝났을때 실행되는 공간(동기적으로)
    // res 에는 객체(status, statusCode, statustext, data, ...)
}).catch((err)=>{
    // get함수가 오류가 발생하면서 끝났을때 실행되는 공간(동기적으로)
    // err에는 에러 객체(에러정보, 에러이름, response, ... )
})

기존 동기함수가 비동기 함수라면??
예를 들어, 함수 중에 ceil이라는 함수가 있다고 가정 해보자.
let res = await ceil(3.5); // 다른거 먼저 하지말고 결과 나올때까지 기다리겠다.
console.log(res + 7);

컴퓨터
ceil(3.5) 아직 결과가 안나왔다면
let res = Promise 타입의 객체를 일단 넣어줌

완전 이거랑 똑같이 동작함
ceil(3.5).then((res)=>{
    console.log(res + 7);
})

Q.이 함수를 어떻게 await로 변경할 수 있을까?
axios.get('/users').then((res)=>{
    console.log(res);
})

let res = await axios.get('/users');
console.log(res);

그런데 한 가지 문제가 있다...만약 오류가 발생한다면?
catch가 실행되어야 하는데
만약 실행하다가 오류가 발생하면 어떻게 되요?
-> try & catch로 감싸주면 된다

try{
    let res = await axios.get('/users');
    console.log(res);
}catch(err){            // 오류가 발생했을때만 이쪽으로 넘어온다
    console.log(err);
}

ex4)
올림, 내림의 결과가 두개가 나온다
doubleMath(3.5).then(a, b)=>{
    console.log(a); // a = 4
    console.log(b); // a = 3
}

let res = await doubleMath(3.5); --> 배열로 [4, 3]
console.log(res[0]);
console.log(res[1]);

구조분해할당
let [a, b] = await doubleMath(3.5); --> 배열로 [4, 3]
console.log(res[a]);
console.log(res[b]);

이걸 왜 해봤냐면...
pool.query('SELECT * from tmp', (err, result, fields)=>{
    if(err !== null){
        console.log(err);
    }else{
        console.log(result);
        console.log(fields);
    }
})

정확히 얘기하면 성공했으면 결과가 2개 나온다
try{
    let [res, fields] = await pool.query('SELECT * from tmp'); // [ , ]
    console.log(result);
    console.log(fields);
}catch(err){
    console.log(err);
}

뭐가 더 편하세요? 취향 차이ㅎㅎㅎ 선택해서 쓰세요~
대신 주의점!
함수 앞에 async 를 꼭 붙여준다!!
  
  async function(){

    }

비밀번호 암호화 시키기

  1. 설치
    npm install bcrypt
  2. import
    const bcrypt = require('bcrypt');
  3. 사용
    app.js 참고

app.js

const express = require('express');
// import express from "express"; 요즘 문법과 동일한데 따로 뭘 깔아줘야 이걸 쓸 수 있음
// const bodyParser = require('bodyParser'); 필요 없음
const mysql = require('mysql2/promise'); // /promise 를 써줘야 async 실행 가능
// const cors = require('cors');
const bcrypt = require('bcrypt');

// require('dotenv').config({path:'../.env.local'}); 아래와 동일함
const dotenv = require('dotenv');
dotenv.config({path:'../.env.local'});

console.log(process.env.DB_USER)

// mysql 설정

// 이걸 깃허브에 올린다고 생각해봐라...다 노출. 그래서 변수를 만들어야한다

const pool = mysql.createPool({
    host: process.env.DB_HOST,  // process 함수 안에 env 라는 key가 있는것
    user: process.env.DB_USER,
    database: process.env.DB_DATABASE,
    password: process.env.DB_PASSWORD,
    port:'3306'
});



const app = express();
const port = 3002;



// app.use(bodyParser().json()); 옛날방식
app.use(express.json());
app.use(express.urlencoded({extended : true}));


// app.use(cors({origin: 'http://localhost:3001'}));

app.get('/api', (req, res)=>{
    res.send('성공했습니다!');
})

// 모든 사원 조회
app.get('/api/employees', (req, res)=>{
    
    // mysql에서 employees 테이블 모든 행, 컬럼 조회
    pool.query('select * from employees', (err, rows, fields)=>{
        console.log(rows);
        // console.log(fields); 컬럼에 대한 정보라 실질적으로 잘 쓰지 않는다
        res.json(rows);
    });

    // res.json();
});

// 사원 한명 추가
app.post('/api/employees', ()=>{});

// 회원 한명 추가
app.post('/api/users', async (req, res)=>{
    console.log(req.body);
    const sql =`INSERT INTO tbl_users
    (email, pw, question, answer)
    VALUES (?, ?, ?, ?)
    `;
    // 구조분해할당해서 넣어버려(이거 좀 어려워 보임)
    let {email, password, question, answer} = req.body;
    // 비밀번호 암호화 시키기
    let enPw = bcrypt.hashSync(password, 10);
    try{
    let [result, fields]= await pool.query(sql, [email, enPw, question, answer]);
        console.log('result', result);
        console.log('fields', fields);
        res.json('Success!!'); // fetch함수는 res.json()
    }catch(err){
        if(err.errno === 1406){
            // 아이디가 컬럼의 최대 허용 용량을 벗어났다 1406
            // 회사마다 에러 넘버가 존재함
            res.status(400).json({errCode : 1, errMsg : '아이디가 너무 김'}); 
        }else if(err.errno === 1062){
        // 아이디가 중복되었다 1062
            res.status(400).json({errCode : 2, errMsg : '아이디가 중복됨'});
        }else{
        // 그외
        console.log(err);
            res.status(400).json({errCode : 3, errMsg: '서버쪽에서 오류 발생함'});
        }
    }
    // MySQL query 실행하기
    // pool.query(`INSERT INTO tbl_users
    // (email, pw, question, answer)
    // VALUES (?, ?, ?, ?)
    // `, [req.body.email, req.body.password, req.body.question, req.body.answer],
    //  (err, result, fields)=>{
    //     if(err !== null){
    //         if(err.errno === 1406){
    //             // 아이디가 컬럼의 최대 허용 용량을 벗어났다 1406
    //             // 회사마다 에러 넘버가 존재함
    //             res.status(400).json({errCode : 1, errMsg : '아이디가 너무 김'}); 
    //         }else if(err.errno === 1062){
    //         // 아이디가 중복되었다 1062
    //             res.status(400).json({errCode : 2, errMsg : '아이디가 중복됨'});
    //         }else{
    //         // 그외
    //             res.status(400).json({errCode : 3, errMsg: '서버쪽에서 오류 발생함'});
    //         }
    //     }else{
    //         console.log('result', result);
    //         console.log('fields', fields);
    //         res.json('성공이야~!!'); // fetch함수는 res.json()
    //     }
    // } 
    // );
    // res.redirect('/'); 예전 백엔드 방식
    // 새로 넣을때 아까 er-error
});

app.listen(port, () =>{console.log(`express 서버 실행됨! 포트:${port}`)});


회원 가입 버튼을 누르니 비밀번호가 잘 암호화되서 DB에 들어간 것을 확인할 수 있다🥰🥰

join.js

import { useState } from "react";
import { AuthBody, AuthBox, AuthForm, BgImg, Button, CancelIcon, ErrMsg, Input, InputBoxWrap, Option, Select, Wrap } from "../../styles/auth/auth.styles";
import CloseIcon from '@mui/icons-material/Close';
import axios from "axios";
import styled from "@emotion/styled";
import { useNavigate } from "react-router-dom";

const JoinPage = ()=>{
    // 원래는 state 변수 9개 만들어야함...
    // 사용자가 email에 입력한 값
    const [email,setEmail] = useState('');
    const [password,setPassword] = useState('');
    const [passwordCheck ,setPasswordCheck] = useState('');
    const [question,setQuestion] = useState(1);
    const [answer,setAnswer] = useState('');

    const [emailErrMsg, setEmailErrMsg] = useState('');
    const [passwordErrMsg, setPasswordErrMsg] = useState('');
    const [PasswordCheckErrMsg, setPasswordCheckErrMsg] = useState('');
    const [answerErrMsg, setAnswerErrMsg] = useState('');


    // 모달창이 열려있는지 닫혀있는지 만들어줄 state변수
    const [isOpen, setIsOpen] = useState(false);

    // 페이지 이동 할때 사용하는 함수
    const navigate = useNavigate(); 
    
    
    const emailInputHandler = (e)=>{
        setEmail(e.target.value);

        const emailText = e.target.value;

        if(emailText === ''){
            setEmailErrMsg('이메일은 필수 입력 값입니다.');
        }else if(!emailText.includes('@')){
            setEmailErrMsg('이메일 형식을 지켜주세요');
        }else{
            setEmailErrMsg('');
        }

    }

    const passwordInputHandler = (e)=>{
        const passwordText = e.target.value;
        setPassword(passwordText);

        if(passwordText === ''){
            setPasswordErrMsg('비밀번호는 필수 입력 값입니다.');
        }else if(passwordText.length < 6){
            setPasswordErrMsg('최소 6글자 이상으로 작성해주세요.');
        }else{
            setPasswordErrMsg('');
        }
        
        if(passwordText !== passwordCheck){
            setPasswordCheckErrMsg('비밀번호가 일치하지 않습니다.');
        } 
    
    
    }


    const passwordCheckInputHandler = (e)=>{
        const passwordCheckText = e.target.value;
        setPasswordCheck(passwordCheckText);

        if(passwordCheckText ===''){
            setPasswordCheckErrMsg('비밀번호 확인은 필수 입력 값입니다.');
        }else if(passwordCheckText !== password){
            setPasswordCheckErrMsg('비밀번호가 일치하지 않습니다.');
        }else{
            setPasswordCheckErrMsg('');
        }
    }

    const onSelectHandler = (e)=>{
        // console.log(e.target);
        setQuestion(parseInt(e.target.value));
    }

    const onAnswerInputHandler = (e)=>{
        setAnswer(e.target.value);
        if(e.target.value === ''){
            setAnswerErrMsg('이메일 찾기 응답은 필수 입력 값입니다');
        }else{
          setAnswerErrMsg('');
        }
    }

    const submitHandler = async (e)=>{
      // submit Event가 발생하면 실행되는함수,
      // e에는 발생한 이벤트 객체가 대입이 된다.
      // e.preventDefault() 함수는 이벤트의 기본 동작을 막는 함수이고,
      // submit 이벤트의 기본 동작은 데이터를 전송하는 것이므로, 전송을 막는 것
      e.preventDefault();
    

      let check = true;

      // 이메일 input 태그 확인
      // state변수 email 확인
      if(email === ''){
        setEmailErrMsg('이메일은 필수 입력 값입니다.');
        check = false;
      }else if(!email.includes('@')){
        setEmailErrMsg('이메일 형식을 지켜주세요');
        check = false;
      }else{
        setEmailErrMsg('');
      }

      // 비밀번호 입력 확인
      // state변수 password 확인
      if(password === ''){
        setPasswordErrMsg('비밀번호는 필수 입력 값입니다.');
        check = false;
      }else if(password.length < 6){
        setPasswordErrMsg('최소 6글자 이상으로 작성해주세요.');
        check = false;
      }else{
        setPasswordErrMsg('');
      }

      // 비밀번호 확인 입력 확인
      // state변수 passwordCheck 확인
      if(passwordCheck ===''){
        setPasswordCheckErrMsg('비밀번호 확인은 필수 입력 값입니다.');
        check = false;
      }else if(passwordCheck !== password){
        setPasswordCheckErrMsg('비밀번호가 일치하지 않습니다.');
        check = false;
      }else{
        setPasswordCheckErrMsg('');
      }
      // 대답 입력 확인
      // state변수 answer 확인
      if(answer === ''){
        setAnswerErrMsg('이메일 찾기 응답은 필수 입력 값입니다');
        check = false;
      }else{
        setAnswerErrMsg('');
      }
      

      // 모든 값들이 정상적으로 입력되었다면
      // submit
      if(check){
        // alert('정상 입력됨!');
        // 서버로 전송!
        // console(e.target); // form 태그가 들어있음
        // e.target.submit();

        try{
          let res = await axios.post('/api/users', {email, password, question, answer})
          console.log(res)
          setIsOpen(true);
        }catch(err){
          console.log(err);
          if(err.response.data.errCode === 1){
            // alert('아이디가 너무 깁니다.');
            setEmailErrMsg('입력 항목에 너무 긴 항목이 있습니다.')
          }else if(err.response.data.errCode === 2){
            setEmailErrMsg('중복된 아이디가 존재합니다.')
          }else{
          alert('서버에 문제가 발생했습니다. 잠시 뒤에 다시 시도해주세요.');
          }
        }



        // axios는 비동기 함수
        // axios.post('/api/users', { 
        //   email, // email:email key와 value가 동일하면 생략가능
        //   password,
        //   question,
        //   answer
        // }).then((res)=>{
        //   console.log(res)
        //   // 회원가입이 성공했다면 실행시킬 요소들
        //   // alert('회원가입 성공했습니다!');
        //   setIsOpen(true);
        // }).catch((err)=>{
        //   console.log(err);
        //   // 회원가입이 실패했다면 실행시킬 코드
        //   if(err.response.data.errCode === 1){
        //     // alert('아이디가 너무 깁니다.');
        //     setEmailErrMsg('아이디가 너무 깁니다.')
        //   }else if(err.response.data.errCode === 2){
        //     setEmailErrMsg('중복된 아이디가 존재합니다.')
        //   }else{
        //   alert('서버에 문제가 발생했습니다. 잠시 뒤에 다시 시도해주세요.');
        //   }
        // });

        console.log('ddddd');
      }
    }

    // 모달창 버튼이 클릭되었을 때
    const onModalClick = ()=>{
      // 로그인 페이지로 이동,
      // 페이지 이동시킬 때는 
      navigate('/login', {replace: true}); 
    }

  return(
    <BgImg>
      <Wrap>
        <CancelIcon><CloseIcon/></CancelIcon>
        <AuthBox>
          <h1>회원가입</h1>
          <AuthBody>
            <AuthForm onSubmit={submitHandler} method="POST" action="/api/users">
              <InputBoxWrap>
                <div className="input-box">
                  <Input name="email" onChange={emailInputHandler} type="text" placeholder="아이디"/>
                  <ErrMsg>{emailErrMsg}</ErrMsg>
                </div>
                <div className="input-box">
                  <Input name="pw" onChange={passwordInputHandler} type="password" placeholder="비밀번호"/>
                  <ErrMsg>{passwordErrMsg}</ErrMsg>
                </div>
                <div className="input-box">
                  <Input onChange={passwordCheckInputHandler} type="password" placeholder="비밀번호 확인"/>
                  <ErrMsg>{PasswordCheckErrMsg}</ErrMsg>
                </div>
                <div className="input-box">
                  <Select name="question" onChange={onSelectHandler}>
                    <Option value={1}>내가 태어난 곳은?</Option>
                    <Option value={2}>어린시절 나의 별명은?</Option>
                    <Option value={3}>나의 강아지 이름은?</Option>
                  </Select>
                  <Input name="answer" onChange={onAnswerInputHandler} type="text" placeholder="이메일을 찾을 때의 질문에 답하세요" />
                  <ErrMsg>{answerErrMsg}</ErrMsg>
                </div>
              </InputBoxWrap>
              <Button>회원가입하기</Button>
            </AuthForm>
          </AuthBody>
        </AuthBox>
      </Wrap>
      <ModalWrap isOpen={isOpen}>
        <Modal>
          <h1>성공!</h1>
          <p>확인을 누르시면 로그인 페이지로 이동합니다</p>
          <button onClick={onModalClick}>확인</button>
        </Modal>
      </ModalWrap>
    </BgImg>
  );
}

const ModalWrap = styled.div`
  
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;

  background-color: rgba(0,0,0,0.8);
/* 조건부 렌더링 */
  display: ${ (props)=>{return props.isOpen ? 'flex' : 'none'} };
  justify-content: center;
  align-items: center;
`;

const Modal = styled.div`
  width: 450px;
  background-color: white;
  border-radius: 16px;

  display: flex;
  flex-direction: column;

  align-items: center;
  padding: 30px;
`;

export default JoinPage;

profile
Slowly but surely

0개의 댓글