21-06-23 Week 2
이번 주는 각자 자기소개 시간을 가진 후 여러 가지 이야기를 했었는데 사실 오늘 너무 피곤했어서 집중 뽝-해서 듣진 못했다. 다음부터는 피곤하더라도 제대로 집중 해야지. 중간에 Ajax 이야기도 나왔었고...
스터디 마치기 마지막에는 Node.js 환경에서 JavaScript에서 실행 시간 재는 방법과, node-fetch 모듈을 사용하여 API에 접속하고 데이터를 가져오는 방법을 공부하는 시간을 가졌다. 마지막엔 숙제도 내주었다.
1. Skeleton Loading
1.1 Intro
페이스북, 혹은 인스타그램에서 다음 게시글 데이터를 띄우기 전에 로딩 화면으로 다음과 같은 현상을 흔하게 보았을 것이다.
이러한 골격 로드 화면을 스켈레톤 로딩 화면(Skeleton Loading Screen)이라고 한다.
1.2 스켈레톤 로딩 화면이란?
스켈레톤 화면이란, 페이지의 레이아웃을 모방하여 만든 사용자 인터페이스이다. 페이지가 로드 완료된 후에 실제 데이터 내용과 유사한 모양으로 페이지가 표시된다.
1.3 스켈레톤 로딩 화면을 사용하는 이유: 장점
이처럼 빈 페이지를 표시하는 대신에 스켈레톤 로딩 화면을 사용하여 콘텐츠가 로드되고 있다고 표시하면, 사용자 입장에서 응용 프로그램의 응답 속도가 빨라지는 것처럼 느끼게 된다.
2. dynamoDB
dynamoDB는 AWS에서 개발한 key-value형 NoSQL 데이터베이스이다. 데이터에 대한 낮은 지연 시간 액세스를 제공하도록 설계되었으며, 웹 페이지에선 다음과 같이 dynamoDB의 장점을 소개하고 있다.
DynamoDB를 사용하면 데이터 규모에 관계없이 데이터를 저장 및 검색하고,
어떤 수준의 요청 트래픽이라도 처리할 수 있는 데이터베이스 테이블을 생성할 수 있습니다.
다운타임 또는 성능 저하 없이 테이블의 처리 능력을 확장 또는 축소할 수 있습니다.
3. JavaScript 실행 시간 측정
3.1 환경 세팅
실행하기 이전에 환경을 구축한다.
$ npm init -y // package.json 생성하기
$ npm i -S node-fetch // node-fetch 모듈 설치하기
$ touch main.js // 빈 파일 main.js 생성하기
3.2 실행 시간 측정하기
- 코드 1
const fetch = require('node-fetch');
const main = async() => {
console.log('hello');
// start
const start = new Date().getTime(); //1991년 이후로 시간이 얼만큼 지났는지 측정
const end = new Date().getTime();
const _interval = end - start;
console.log(_interval);
};
main();
/* 실행 결과 */
// 0 :너무 짧아서 안나온다
- 코드 2
const fetch = require('node-fetch');
const main = async() => {
console.log('hello');
// start
const start = new Date().getTime(); //1991년 이후로 시간이 얼만큼 지났는지 측정
/* 1초 후 실행 */
setTimeout(() => {
const end = new Date().getTime();
const _interval = end - start;
console.log(_interval);
}, 1000);
const _interval = end - start;
console.log(_interval);
};
main();
/* 실행 결과 */
// 1004
4. API 접근하기: node-fetch
4.1 node-fetch
fetch API란 서버와의 비동기 요청방식으로, Ajax의 방식 중에서도 최신 방식이다. 기본 코드는 다음과 같다.
참고로 fetch API는 promise 방식의 기반이다. 더 자세한 사용 방법을 알아보자.
4.2 node-fetch를 사용하여 API 접근하기
node-fetch를 사용하여 API에 접근하여 데이터를 요청하여 가져오는 코드를 작성해보자. API는 다음과 같다.
API
domain: https://zelight.vercel.app/
(required)
- headers
- permission: YYYYMMDD
Api1:
- endpoint: /api/20210608/getUserIds
- method: GET
- query: {}
- res
{
success: { type: Boolean },
error: { type: String },
data: {
userIds: { type: [String] },
},
}
Api2:
- endpoint: /api/20210608/getUserDetails
- method: GET
- query: { userId: { type: String } }
- res
{
success: { type: Boolean },
error: { type: String },
data: {
user: {
id: { type: String } ,
name: { type: String },
major: { type: String },
studentId: { type: String },
graduation: { type: Boolean },
},
},
}
Api3:
- endpoint: /api/20210608/getUserOriginByName
- method: GET
- query: { userName: { type: String } }
- res
{
success: { type: Boolean },
error: { type: String },
data: {
name: { type: String },
Origin: { type: String },
},
}
다음은 getUserIds에 접속하여 모든 유저들의 데이터를 가져오는 코드이다.
const fetch = require('node-fetch');
const baseUrl = 'https://zelight.vercel.app';
const API_1 = `${baseUrl}/api/20210608/getUserIds`;
const main = async() => {
/* task */
const res = await fetch(API_1, {
headers: {
'permission': "20210622",
}
});
const json_1 = await res.json();
console.log(json_1);
/* task */
};
main();
- 실행 결과 콘솔
5. week2: 숙제 설명 & 예습
5.1 숙제 설명
목표
- JavaScript의 콜백, 프로미스 완전히 배우기!
숙제 설명
- node-fetch를 사용하여 API에 접근한다.
- (1) getUserIds에 접근하여 모든 유저들의 id 데이터를 얻는다.
- (2) query값으로 {userId: id}값을 넣고 getUserDetails에 접근하여 모든 유저들의 name 데이터를 얻는다.
- (3) query값으로 {userName: name}값을 넣고 getUserOriginByName에 접근하여 모든 유저들의 Origin(학과) 데이터를 출력한다.
- 조건 1: (1)~(3)까지 과정을 거쳐 Origin(학과) 데이터를 출력하기까지 실행 시간을 측정했을 때, 측정된 시간 값이 4s(=4000) 이내여야 한다.
- 조건 2: API에 접속할 때에는 headers에 {'permission': '20210622'} 값을 주어야만 데이터에 접근할 수 있다!
- 💡 Hint 1: getUserOriginByName 주소 요청 시, url에 한글값을 넣을 땐 인코딩 과정을 거쳐야 한다.
API
domain: https://zelight.vercel.app/
(required)
- headers
- permission: YYYYMMDD
Api1:
- endpoint: /api/20210608/getUserIds
- method: GET
- query: {}
- res
{
success: { type: Boolean },
error: { type: String },
data: {
userIds: { type: [String] },
},
}
Api2:
- endpoint: /api/20210608/getUserDetails
- method: GET
- query: { userId: { type: String } }
- res
{
success: { type: Boolean },
error: { type: String },
data: {
user: {
id: { type: String } ,
name: { type: String },
major: { type: String },
studentId: { type: String },
graduation: { type: Boolean },
},
},
}
Api3:
- endpoint: /api/20210608/getUserOriginByName
- method: GET
- query: { userName: { type: String } }
- res
{
success: { type: Boolean },
error: { type: String },
data: {
name: { type: String },
Origin: { type: String },
},
}
(+) 서버 요청 시 한글 url 파라미터 사용하기
- 완성 코드
const fetch = require('node-fetch');
const baseUrl = 'https://zelight.vercel.app';
const API_1 = `${baseUrl}/api/20210608/getUserIds`;
const API_2 = `${baseUrl}/api/20210608/getUserDetails`;
const API_3 = `${baseUrl}/api/20210608/getUserOriginByName`;
const pms = '20210626';
(async() => {
const start = new Date().getTime();
const res = await fetch(API_1, {
headers: {'permission':pms}
});
const json_1 = await res.json();
const userIds = json_1['data']['userIds'];
await Promise.all(userIds.map(async(userId) => {
const res2 = await fetch(`${API_2}?userId=${userId}`, {
headers: {'permission':pms}
});
const json_2 = await res2.json();
const userName = json_2['data']['user']['name'];
const res3 = await fetch(`${API_3}?userName=${encodeURIComponent(userName)}`,{
headers: {'permission':pms}
});
const json_3 = await res3.json();
console.log(json_3['data']['origin']);
}));
const end = new Date().getTime();
const _interval = end - start;
console.log('interval: ', _interval);
})();
- 실행 결과
5.2 예습
예습할 부분: JavaScript
- 자료형
- 반복문, 조건문
- 콜백
- 프로미스
예습 공부
1. 자료형
- Number
- Boolean
- String
- Symbol
- null
- undefined - 변수에 값을 선언하지 않으면 undefined로 지정된다.
- Object
2. 반복문, 조건문
- 조건문
- if
- else
- switch
- 반복문
- for
- for in
- for of
- while
3. 콜백
▪ 비동기 처리
자바스크립트는 동기 처리이다. 하지만 비동기 함수가 들어오면 그 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행한다. 자바스크립트의 이러한 특성 때문에 비동기 코드를 동기 처리를 해야하는 경우에 문제가 발생한다.
- ajax 통신: 화면에 표시할 이미지나 데이터를 서버에서 불러와서 표시해야 하는 경우에 ajax 통신으로 해당 데이터를 서버로부터 가져온다. 이때 데이터를 서버로부터 받아오고 나서 처리를 해야하는데, 자바스크립트의 비동기 처리 특성에 의하면 데이터를 받아오는데 걸리는 시간이 길어서 다음 코드를 먼저 실행하게 된다. 그래서 ajax 통신 코드를 동기 처리로 작성하게 되면 아직 값을 받아오지 않았으므로 undefined가 출력된다.(getData 함수 내에 비동기가 있는 반면에 console.log는 동기함수라서 바로 실행되기 때문임)
function getData() {
var tableData;
$.get('https://domain.com/products/1', function(response) {
tableData = response;
});
return tableData;
}
console.log(getData()); // undefined
▪ 콜백 함수
이러한 비동기 처리를 동기적으로 처리하려 할 때 발생하는 문제점을 콜백 함수로 해결할 수 있다. 비동기 함수가 실행되고 난 후에 실행될 코드를 ajax의 콜백함수 안에 넣어주는 거다.
function getData(callbackFunc) {
$.get('https://domain.com/products/1', function(response) {
callbackFunc(response); // 서버에서 받은 데이터 response를 callbackFunc() 함수에 넘겨줌
});
}
getData(function(tableData) {
console.log(tableData); // $.get()의 response 값이 tableData에 전달됨
});
이렇게 콜백 함수를 사용하면 특정 로직이 끝났을 때 원하는 동작을 실행시킬 수 있다. 하지만 동기 처리를 하기 위해서 콜백 함수를 연속적으로 사용할 때 콜백 지옥(Callback hell) 문제가 발생하기도 한다.
$.get('url', function(response) {
parseValue(response, function(id) {
auth(id, function(result) {
display(result, function(text) {
console.log(text);
});
});
});
});
(으악 옆으로 길어진다!)
웹 서비스를 개발하다 보면 서버에서 데이터를 받아와 화면에 표시하기까지 인코딩, 사용자 인증 등을 처리해야 하는 경우가 있다. 만약 이 모든 과정을 비동기로 처리한다면 위의 코드와 같이 콜백 안에 콜백을 계속 무는 형식으로 코딩을 하게 된다. 이러한 코드 구조는 가독성도 떨어지고 로직을 변경하기도 어렵다. 이와 같은 코드 구조를 콜백 지옥이라고 한다.
코딩 패턴으로만 콜백 지옥을 해결하려고 하면 아래와 같이 각 콜백 함수를 분리해줄 수도 있다.
function parseValueDone(id) {
auth(id, authDone);
}
function authDone(result) {
display(result, displayDone);
}
function displayDone(text) {
console.log(text);
}
$.get('url', function(response) {
parseValue(response, parseValueDone);
});
중첩하여 선언했던 콜백 익명 함수를 각각의 함수로 구분하여 정리한 코드이다. 하지만 일반적으로 콜백 지옥을 해결하기 위해서 Promise나 Async를 사용한다.
4. 프로미스(Promise)
프로미스는 자바스크립트의 비동기 코드에 사용되는 객체이다. 프로미스는 주로 서버에서 받아온 데이터를 화면에 표시할 때 사용한다. 위에서 소개한 ajax 통신 코드에 프로미스를 적용하면 다음과 같은 코드가 된다.
function getData(callback) {
// new Promise() 추가
return new Promise(function(resolve, reject) {
$.get('url 주소/products/1', function(response) {
// 데이터를 받으면 resolve() 호출
resolve(response);
});
});
}
// getData()의 실행이 끝나면 호출되는 then()
getData().then(function(tableData) {
// resolve()의 결과 값이 여기로 전달됨
console.log(tableData); // $.get()의 reponse 값이 tableData에 전달됨
});
콜백 함수로 처리하던 구조에서 new Promise(), resolve(), then()와 같은 프로미스 API를 사용한 구조로 바뀐다. 프로미스에는 Pending(대기), Fulfilled(이행), Rejected(실패) 세 가지 상태가 있다.
- Pending(대기): new Promise() 메서드를 호출하면 기본적으로 대기 상태가 된다. 메서드를 호출할 때 콜백 함수로 선언하며, 콜백 함수의 인자는 resolve, reject이다.
new Promise(function(resolve, reject) {
// ...
});
- Fulfilled(이행): 콜백 함수의 인자 resolve를 실행하면 이행 상태가 된다. 그리고 이행 상태가 되면 then()으로 처리한 결과 값을 받을 수 있다.
function getData() {
return new Promise(function(resolve, reject) {
var data = 100;
resolve(data);
});
}
// resolve()의 결과 값 data를 resolvedData로 받음
getData().then(function(resolvedData) {
console.log(resolvedData); // 100
});
- Rejected(실패): 프로미스 인자인 reject를 호출하면 실패 상태가 된다. 그리고 실패 상태가 되면 실패한 이유(실패 처리의 결과 값)를 catch()로 받을 수 있다.
function getData() {
return new Promise(function(resolve, reject) {
reject(new Error("Request is failed"));
});
}
// reject()의 결과 값 Error를 err에 받음
getData().then().catch(function(err) {
console.log(err); // Error: Request is failed
});
▪ 프로미스 예제: ajax 통신
function getData() {
return new Promise(function(resolve, reject) {
$.get('url 주소/products/1', function(response) {
if (response) {
resolve(response);
}
reject(new Error("Request is failed"));
});
});
}
// 위 $.get() 호출 결과에 따라 'response' 또는 'Error' 출력
getData().then(function(data) {
console.log(data); // response 값 출력
}).catch(function(err) {
console.error(err); // Error 출력
});
함수 getData 안에는 비동기 함수인 Promise가 들어있다. 이 Promise가 resolve가 될 때까지 기다렸다가 resolve가 되면 then이 실행된다. 그래서 ajax를 통해 데이터를 받아온 후에 then이 실행되어 콘솔을 실행하고, 데이터 받아오기를 실패했다면 reject가 되어 catch가 실행되어 이에 따른 에러 콘솔을 띄운다.
▪ 여러 개의 프로미스 연결하기 (Promise Chaining)
프로미스는 여러 개의 프로미스를 연결하여 사용할 수 있다는 특징을 가진다. .then() 메서드를 호출하고 나면 새로운 프로미스 객체가 반환되기 때문에 다음과 같은 코딩이 가능하다.
new Promise(function(resolve, reject){
setTimeout(function() {
resolve(1);
}, 2000);
})
.then(function(result) {
console.log(result); // 1
return result + 10;
})
.then(function(result) {
console.log(result); // 11
return result + 20;
})
.then(function(result) {
console.log(result); // 31
});
새로운 프로미스 객체로 반환된다고 했는데 이건 무슨 의미지? 할 수 있는데. 프로미스를 반환할 때 반환하는 값이 일반 값이면 그냥 바로 resolve한 것과 같다. 그래서 얘는
return result + 10
아래랑 동일한 거다.
return Promise.resolve(result + 10)
만약 반환하는 값이 프로미스라면 그 프로미스를 resolve한 값이 다음 then으로 넘어간다.
▪ 실무 프로미스 연결 사례
getData(userInfo)
.then(parseValue)
.then(auth)
.then(diaplay);
var userInfo = {
id: 'test@abc.com',
pw: '****'
};
function parseValue() {
return new Promise({
// ...
});
}
function auth() {
return new Promise({
// ...
});
}
function display() {
return new Promise({
// ...
});
}
근데 이런 경우에 만약 함수를 실행시키는데 순서를 지킬 필요 없이 동시에 실행 시켜도 상관이 없는 코드다, 하면 Promise.allSettled([프로미스1, 프로미스2, ...]) 혹은 Promise.all([프로미스1, 프로미스2, ... ])로 작성해서 동시에 실행시키자. 그래야 더 빨리 실행된다.
(+) 5. async & await
async와 await는 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법이다. 기존의 콜백 함수와 프로미스의 단점을 보완하고, 개발자가 읽기 좋은 코드를 작성하도록 도와준다.
▪ 기본 문법
async function 함수명() {
await 비동기_처리_메서드_명();
}
먼저 함수 앞에 'async'라는 예약어를 붙인다. 그리고 함수 내부 로직 중, http 통신을 하는 비동기 처리 코드 앞에 'await'를 붙인다. 단, 비동기 처리 메서드가 꼭 프로미스 객체를 반환해야만 await가 의도한 대로 동작한다. 일반적으로 await의 대상이 되는 비동기 처리 코드로는 Axios 등 프로미스를 반환하는 API호출 함수이다.
▪ 예제
function fetchUser() {
var url = 'https://jsonplaceholder.typicode.com/users/1'
return fetch(url).then(function(response) {
return response.json();
});
}
function fetchTodo() {
var url = 'https://jsonplaceholder.typicode.com/todos/1';
return fetch(url).then(function(response) {
return response.json();
});
}
async function logTodoTitle() {
var user = await fetchUser();
if (user.id === 1) {
var todo = await fetchTodo();
console.log(todo.title); // delectus aut autem
}
}
then을 await으로 바꾼거랑 동일하다. 대신 비동기 함수로부터 결과값으로 가져온 값들을 동시에 사용 가능하다.
이게 무슨 소리냐면, 콜백함수의 경우에는 비동기 함수로부터 resolve된 값만 다음 값으로 넘어가기 때문에 한 가지의 값만 사용할 수 있는데
Promise의 경우에는 여러 값들을 순서대로 가져와서 여러 변수에 저장해놓고 이 변수들을 가지고 여러 조작이 가능하다.
참고 자료
'Education' 카테고리의 다른 글
[개발스터디] week3: Next.js로 서버 만들기 / React.js (0) | 2021.06.30 |
---|---|
[웹개발종합반] 강의을 마치며 (0) | 2021.06.27 |
[웹개발종합반] week5: AWS / FileZilla (0) | 2021.06.18 |
[웹개발종합반] week4: Flask (0) | 2021.06.18 |
[웹개발종합반] week3: Python / 크롤링 / mongoDB (0) | 2021.06.18 |