(37) 23.11.14[JS]

DANA·2023년 11월 14일

KDT-구디아카데미

목록 보기
37/56

Async_await

자바스크립트는 객체지향언어가 아니야
: 스레드 이슈 - 동기와 비동기
: 생성자, 메소드-클래스 안에 정의된 전역함수를 메소드라 함
자바스크립트 프로토타입 기반 언어다
모든 객체들이 메소드와 속성들을 상속받기 위한 템플릿이다
프로토타입 객체도 상위 프로토타입으로부터 상속받을 수도 있다
프로토타입 체이닝이 발생한다
:다른 객체에 정의된 메소드와 속성을 다른 객체에서 재사용할 수 있는 근간이다
자바스크립트는 동기다.
그래서 중간에 지연상황이 발동하여도 아래로 아래로 진행이 된다.
서버측에서 받아온 정보를 담지 못했다
왜냐면 시간차가 발생했으니까
시간차를 극복하는 방법은 - 비동기[콜백, 프로토타입, 반환값-화면출력함]처리를 해라

그런데 비동기로 처리해야 하는 경우가 있다
setTimeout함수(1:콜백,2:시간-ms) - 지연 - await
강제로 지연상황을 연출해 보았다

어떤 경우가 있을까?

  • 날씨정보를 서버(http프로토콜-네트워크-IO-dead lock-재시작)로부터 받아온다
  • OMDB API - 영화정보 요청(키와 frozen-파라미터-유저가 입력)- 입력에 대한 응답(성공(resolve),실패(reject-에러 핸들링))

처리할 수 있는 키워드
setTimeout - 쓸 수 있지만 후지다
Promise 배웠음 - 사이드 주제: 에러핸들링 기법이나 코드 전개 모방
오늘은 async&await
Fetch()

콜백함수 자리함 - 두 번째 인자만큼의 지연 후에 실행됨

프로세스가 많아질수록 setTimeout이 중첩되어가고
또 들여쓰기도 depth 자꾸만 깊어져서
가독성이 떨어지고 유지보수 어렵다

개선할 수 있는 방법이 있나?
Promise - 반복되는 코드를 줄이고, 보다는 가독성도 괜찮은 편

Promise
성공시 - resolve - 반환타입은 JSON인가? 아님 Object인가?
실패시 - reject - 에러핸들링은 어떻게 처리할 수 있을까?


기본.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./기본.js"></script>
</head>
<body>
  기본 화면
</body>
</html>

기본.js

// Async, Await
// then 함수를 쓰려면 Promise이어야 한다 - then은 Promise 프로토타입이 제공하는 함수다
// 여기서 프로토타입에 대한 정의는 뭘까?
// 어떤 객체에 대해서도 재사용이 가능한 객체인 것
// fetch함수는 BOM이라서 REPL 실행이 불가하다
// console.log(fetch("http://www.omdbapi.com/?apikey=fae5231a&s=avatar&page=2"));

// 실습코드 작성 목적 
// 코드 a는 1초 후에 실행되는 비동기 코드이다 - 코드를 파악
// 그렇게(내 안에 setTimeout) 만들어진 비동기 코드가 실행되고 나서 
// b함수의 실행을 보장 받으려면 then이라는 prototype의 메소드를 사용해서 내용을 작성할 수 있다

const a = () => {
  return new Promise(resolve => {
    setTimeout(()=>{
      console.log(1);
      resolve()
    },1000)
  })
}

const b = () => console.log(2);

a().then(()=> b());
// Cannot read properties of undefined(힌트)
// 왜냐하면 a는 리턴타입이 Promise 인스턴스라서 Promise.prototype.then()을 호출할 수 있다
// 이와 반대로 b는 달랑 출력하는 문이 전부다 - Promise 인스턴스를 갖지 못했다 
// b().then(()=> a());

기본1.js

// Async, Await

const a = () => {
  return new Promise(resolve => {
    setTimeout(()=>{
      console.log(1);
      resolve()
    },1000)
  })
}

const b = () => console.log(2);

// a().then(()=> b()); 1과 2 순서를 보장받았다는


const wrapping = async () => {
  await a(); // 그 뒤에 있는 비동기 함수의 실행을 기다릴게
  b();
}

wrapping();

영화1.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Promise 실습 - OMDB API</title>
  <script src="./영화1.js"></script>
</head>
<body>
  
</body>
</html>

영화1.js

// OMDB - API 활용 실습

const movieList = (movieTitle) => {
  return new Promise(resolve => {
    fetch(`http://www.omdbapi.com/?apikey=fae5231a&s=${movieTitle}`)
    .then(response => response.json())
    .then(response => {
      console.log(response);
      resolve()
    })
  })
}

movieList('avatar') // 
  .then(() => {
    console.log('아바타');
    return movieList('avengers')
  })
  .then(()=> {
    console.log('어벤져스');
    return movieList('frozen')
  })
  .then(()=>{
    console.log('겨울왕국');
  })

영화2.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Async&Await실습 - OMDB API</title>
  <script src="./영화2.js"></script>
</head>
<body>
  
</body>
</html>

영화2.js

// OMDB - API 활용 실습

const movieList = (movieTitle) => {
  return new Promise(resolve => {
    fetch(`http://www.omdbapi.com/?apikey=fae5231a&s=${movieTitle}`)
    .then(response => response.json())
    .then(response => {
      console.log(response);
      resolve()
    })
  })
}

/* 
주의사항
async와 await 커플이다. 한쌍이다
console.log 앞에는 await을 사용할 수 없다?
왜냐면 콘솔로그는 Promise 인스턴스를 반환하지 않으니까. 그러니까 안 된다
async를 붙이는 자리는 콜백함수 자리다.
Promise 사용한 구문을 async와 await으로 변경하기 위해서는 
await을 감싸는 함수를 선언할 것
*/

const wrapping = async () => {
  await movieList('avatar')
  console.log('아바타');
  await movieList('avengers')
  console.log('어벤져스');
  await movieList('frozen')
  console.log('겨울왕국');
}
// 주의 - 함수 호출하기
wrapping();

// 테스트 할 때 주의사항 - fetch 함수는 BOM API라서 REPL로는 확인 불가함
// 그러니 html 문서를 통해서 실행할 것

기본 예제에서 영화 예제까지 관찰하였다
1. fetch 함수는 반드시 html 문서 필요하다
2. fetch 함수의 반환타입이 Promise라는 걸 알게되었다

Promise는 다음 중 하나의 상태를 가집니다.
(변하니까 상태라고 한다)
대기(pending): 이행하지도, 거부하지도 않은 초기 상태. - 결과 바랄 수 없다 - 애매모호한
: pending상태에 놓여있다면 코딩을 덜 작성한 것임
이행(fulfilled): 연산이 성공적으로 완료됨. - 200번(OK상태) / 비동기로 치면 success인 상태. 이때 후처리를 진행한다
: 영화예제에서 전처리는 뭐였지? - 영화제목을 입력받는 일
거부(rejected): 연산이 실패함.
// 인자가 함수라는 건 실행문을 가질 수 있다는 것
// {}로 묶을 수 있다 - 이래야 코드가 전개되기 시작하니까
// reject()는 에러를 핸들링 할 수 있다 -> try..catch 블록 -> 이걸 안 하면 곡개이 403 날것으로 보게 됨
// 화면처리에 순서가 중요하다
// 생성자 안에서 initDisplay()호출,
// 영화정보를 조건 검색하는데 화면이 먼저인가요? 아님 fetch 처리가 먼저일까요?
// 자바스크립트는 먼저 흘러간다 - 화면이 그려지는게 먼저다 - 그런데 영화정보가 없는 화면이다
// 문제제기 - 비동기로 처리해야 되는 걸 알게 된다
new Promise((resolve,reject))

youtubeFetch.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Youtube-step1</title>
  <script src="./youtubeFetch.js" defer></script>
  <link rel = "stylesheet" href = "./youtube.css" />
</head>
<body>
  <div id="root">있으나마나 덮어씌워짐</div>
</body>
</html>

youtubeFetch.js(1)

const requestOptions = {
  method: 'GET',
  redirect: 'follow'
}

fetch("https://www.googleapis.com/youtube/v3/videos?part=snippet&key=AIzaSyCbfvxVnTcUdnDl-L3Zp2el4xOgGXqgk44&maxResults=3&chart=mostPopular", requestOptions)
  .then(response => {
    const data = response.json();
    console.log(data);
    console.log(data.kind);
  })
  .catch(error => console.log('error', error));

youtubeFetch.js(2)

const requestOptions = {
  method: 'GET',
  redirect: 'follow'
}

fetch("https://www.googleapis.com/youtube/v3/videos?part=snippet&key=AIzaSyCbfvxVnTcUdnDl-L3Zp2el4xOgGXqgk44&maxResults=3&chart=mostPopular", requestOptions)
  .then(async response => {
    const data = await response.json();
    console.log(data);
    console.log(data.items);
    console.log(JSON.stringify(data));
  })
  .catch(error => console.log('error', error));

youtubePromise.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Youtube-step1</title>
  <script src="./youtubePromise.js" defer></script>
  <link rel = "stylesheet" href = "./youtube.css" />
</head>
<body>
  <input type="button" value="인기동영상" onclick="wrapping()">
  <div id="root">있으나마나 덮어씌워짐</div>
</body>
</html>

youtubePromise.js

const mostVideo = () => {
  return new Promise((resolve,reject) => {
    fetch("https://www.googleapis.com/youtube/v3/videos?part=snippet&key=AIzaSyCbfvxVnTcUdnDl-L3Zp2el4xOgGXqgk44&maxResults=3&chart=mostPopular", requestOptions)
      .then(res => {
        const data = res.json();
        console.log(data);
        console.log(data.items);
      })
      .catch(error => console.log('error', error));
  })
}

const wrapping = async() => {
  await mostVideo()
}

유효성1.js

// Resolve, Reject 흉내내기

const validFunc = (index,cb,errorCb) => {
  setTimeout(()=>{
    if(index > 10){
      errorCb(`${index}는 10보다 클 수 없습니다.`);
      return;
    }
    console.log(index);
    cb(index+1)
  },1000)
}
// 함수의 파라미터가 3개가 있는데 
// 조건에 따라서 호출되는 콜백 함수를 제어할 수 있다

// insert here
validFunc(4,res => console.log(res),err => console.log(err));

validFunc(15,res => console.log(res),err => console.log(err));

유효성2.js

// Resolve, Reject 흉내내기
// 아래 함수 내부에서 fetch를 통해 스프링 서버에게 요청을 할 수 있다 -
// SELECT deptno, dname, loc FROM 부서집합 WHERE deptno=10 or dname='Sales'
// resolve(res)- resolve에 태워진 저 데이터셋은 어디로 가는 걸까?
// 화면{html}으로 가서 출력하는데 데이터값으로 존재함 - 연결
// resolve 함수에 태워진 데이터셋이 반환됨 -> 그 반환된 정보가 td태그 안에 데이터가 꽂히면 데이터

const validFunc = (index) => {
  return new Promise((resolve,reject) => {
    // fetch 오는 자리로 쓸 수 있음 - url 값이다 - 
    setTimeout(()=>{
      if(index > 10){
        reject(`${index}는 10보다 클 수 없습니다.`);
        return;
      } // end of if
      console.log(index);
      resolve(index+1)
    },1000) // end of setTimeout
  })
  } // end of validFunc

  validFunc(5)
    .then(res => console.log(res))
    .catch(err => console.error(err))

    validFunc(15)
    .then(res => console.log(res))
    .catch(err => console.error(err))

유효성3.js

const validFunc = (index) => {
  return new Promise((resolve,reject) => {
    setTimeout(()=>{
      if(index > 10){
        reject(`${index}는 10보다 클 수 없습니다.`);
        return;
      } // end of if
      console.log(index);
      resolve(index+1)
    },1000) // end of setTimeout
  })
  } // end of validFunc

  validFunc(5)
    .then(res => console.log(res))
    .catch(err => console.error(err))
    .finally(()=> console.log('처리완료'))

  // 학습목표
  // async와 await으로 처리할 때 에러처리는 try..catch 블록으로 처리한다

    const wrapping = async () => {
      try {
        const res = await validFunc(15);
        console.log(res);
      } catch (error) {
        console.error(error);
      } finally {
        console.log('정상이거나 실패인 경우 모두 항상 실행됨');
      }
    }
    wrapping();

영화3.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>에러핸들링 실습 - Promise</title>
  <script src="./영화3.js"></script>
</head>
<body>
  
</body>
</html>

영화3.js

// OMDB - API 활용 실습

const movieList = (movieTitle) => {
  return new Promise((resolve,reject) => {
    fetch(`http://www.omdbapi.com/?apikey=fae5231a&s=${movieTitle}`)
    .then(res => res.json())
    .then(json => {
      if(json.Response === 'False'){
        reject(json.Error)
      }
      resolve(json)
    })
    .catch(error => {
      reject(error);
    })
  }) // end of Promise
} // end of movieList

let loading = true;

movieList('avatar')
  .then(movies => console.log(movies))
  .catch(error => console.log(error))
  .finally(()=> loading = false)

*영화 3 파일들 다시 확인해보기


youtube.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>조건검색-step3</title>
  <link rel="shortcut icon" href="../../../images/youtube/logo.ico" type="image/x-icon">
  <script src="./youtube.js" defer></script>
  <link rel = "stylesheet" href = "./youtube.css" />
</head>
<body>
  <h3>유튜브 클론코딩 실습3</h3>
  <header class="header">
    <div class="logo">
      <img src="../../../images/youtube/logo.png" alt="logo" />
      <h1 class="title">Youtube</h1>
    </div>
    <input
      class="input"
      type="search"
      name="keyword"
      id="keyword"
      placeholder="Search..."
    />
    <button class="btnSearch">
      <img
        class="imgSearch"
        src="../../../images/youtube/search.png"
        alt="search"
      />
    </button>
  </header>
  <div id="root">있으나마나 덮어씌워짐</div>
</body>
</html>

youtube.css

* {
  box-sizing: border-box;
  /* border: 1px solid red; */
}

.header {
  display: flex; /* 요소들을 가로로 펼쳐서 배치할 때 */
  height: 4rem;
  padding: 0.5rem 1rem;
  background-color: black;
}

.logo {
  display: flex;
  align-items: center;
  margin-right: 14px;
}

.input {
  flex-basis: 100%;
  font-size: 1.2rem;
  outline: 0;
}

.btnSearch {
  background-color: darkgray;
  outline: 0;
}

.imgSearch {
  height: 100%;
  padding: 0.5rem 0.7rem;
}

/* ul태그 작성 */
.videos {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  list-style: none;
  padding-left: 0px;
  margin: 0px;
  gap: 14px;
}

/* li태그 css 작성 */
.video {
  display: flex; /* 방향은 디폴트 row */
  align-items: center;
  border: 1px solid lightgray;
  box-shadow: 3px 3px 5px 0px rgba(191, 191, 191, 0.53);
  transition: transform 250ms ease-in;
  cursor: pointer
}

.video:hover {
  transform: scale(1.02); /* 마우스 오버시 확대처리 */
}

.thumbnail {
  width: 40%;
}

.title,
.channelTitle {
  margin: 10px;
  font-size: 1.0rem;
}

.channelTitle {
  font-size: 0.8rem;
}

youtube.js

// 사용자가 입력한 키워드를 담기 위해 변수 선언
const keyword = document.querySelector('#keyword'); // 이벤트 소스의 주소번지 얻기

// 그 키워드가 이벤트 소스. 내장함수를 사용해서 keydown이라는 이벤트를 처리 - 이때 콜백함수 만남
keyword.addEventListener('keydown',(event)=> {
  // 사용자가 누른 키보드 아스키코드값 10진수가 출력됨
  console.log(event.keyCode); // 78번 - 대문자 N
  // 키다운이 눌렸을 때 사용자가 입력한 검색어를 찾는 함수 호출됨 - 그래야 자동완성기능을 흉내내보기 가능하니까
  handleSearch();
})

// 비디오 재생하는 화면 그려주는 템플릿
// 썸네일 클릭했을 때 상세보기 화면 처리 템플릿

// 사용자가 입력한 검색어 읽어오기
const handleSearch = () => {
  const user = keyword.value; // 사용자가 입력한 글자 얻어옴
  console.log('사용자가 입력한 검색어 =====> '+user);
  // 사용자가 입력한 검색어로 조건 검색 구현하기
  search(user);
}

const search = (query) => {
  console.log('사용자가 입력한 nba를 받아오기: '+query);
  // insert here - 검색 결과를 가져오는 코드 작성하기
  
  const ajax = new XMLHttpRequest();
  const Youtube_URL = `https://www.googleapis.com/youtube/v3/search?part=snippet&key=AIzaSyASLmeLY_14dGRPvYbB5MdVgdbUOm8vZHA&type=video&maxResults=25&q=${query}`;
  ajax.open('GET', Youtube_URL, false);
  ajax.send();
  
  // 배열을 사용하여 템플릿을 구성해본다 1-2
  const videoList = [];
  
  // console.log(ajax.response);
  
  const most = JSON.parse(ajax.response);
  const items = most.items;
  
  let items2;
  items2 = most.items.map((item) => ({...item,id:item.id.videoId}));
  
  console.log(items); //id {kind: }
  console.log(items2); //id
  console.log(items2.length); // 
  videoList.push(`<ul class="videos">`);

  items2.map((video) => {
    //25번 반복 - 25개의 li
    videoList.push(`<li class="container"token interpolation">${video.id}','${video.snippet.title}','${video.snippet.description}','${video.snippet.channelTitle}')">`);

    videoList.push(`<div class="video">`);
    videoList.push(`<img class="thumbnail" src='${video.snippet.thumbnails.medium.url}'/>`); 
    videoList.push(`<div>`);
    videoList.push(`<p class="title">${video.snippet.title}</p>`);
    videoList.push(`<p class="channelTitle">${video.snippet.channelTitle}</p>`);
    videoList.push(`</div>`);
    videoList.push(`</div>`);
    videoList.push(`</li>`);
  })
  videoList.push(`</ul>`);
  
  document.querySelector('#root').innerHTML = videoList.join('');
}

달력API

*vscode 터미널에 npm install fullcalendar 입력하면 node_modules 생성

basic.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>기본달력 테스트 - 실습</title>
  <!-- cdn 방식 링크 -->

  <script src='https://cdn.jsdelivr.net/npm/fullcalendar/index.global.min.js'></script>
  <script>

    document.addEventListener('DOMContentLoaded', function() {
      const calendarEl = document.querySelector('#calendar')
      // calendar 객체 생성 및  생성자 호출 - 초기화 속성값 지정
      const calendar = new FullCalendar.Calendar(calendarEl, {
        initialView: 'dayGridMonth',
        headerToolbar: {
          left: 'prev, next, today',
          center: 'title',
          right: 'dayGridMonth, timeGridWeek, listWeek'
        },
        // 일정에 대한 데이터셋 가져오기
        events:[
          {
            title: '월간회의',
            start: '2023-11-01'
          },
          {
            title: 'other event',
            start: '2023-11-01',
            end: '2023-11-04'
          },
          {
            title: '휴가일정',
            start: '2023-11-07',
            end: '2023-11-15'
          },
          {
            groupId: 999,
            title: '업체회의',
            start: '2023-11-17T15:00:00',
          },
          {
            groupId: 999,
            title: '기획회의',
            start: '2023-11-17T17:00:00',
          }
        ]


      })
      calendar.render() // 위에서 초기화된 정보로 달력을 그려줘
    })

  </script>
  
</head>
<body>
  <div id="calendar"></div>
</body>
</html>

main.js

console.log('일정관리시스템');

package.json

{
  "name": "fullcalendar-step1",
  "version": "1.0.0",
  "description": "카렌더 라이브러리소개",
  "main": "main.js",
  "scripts": {
    "test": "node main.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "fullcalendar": "^6.1.9"
  }
}

0개의 댓글