[코딩온] 프론트엔드 입문 Day 28 (JS API, 동기/비동기, JSON)
원래는 수업 끝나고 집에 오자마자 기록했어야 하는데, 집에 도착하기가 무섭게 컨디션이 바닥을 찍어서 어제는 그냥 쉬고 주말 아침 일찍부터 컴퓨터를 켰다.😅 어제 수업에서는 API와 JSON에 대해서 배웠다. API는 정말 어렴풋하게 알고 있었고 JSON은 아예 모르는 거였어서, 수업을 들으며 시야가 한 층 넓어진 기분이었다.
API (Application Programming Interface)
한 프로그램이 다른 프로그램과 상호작용하는 방법을 정의하는 일련의 규칙과 명세, 혹은 개발자가 소프트웨어를 제어하기 위해 필요한 함수를 모아놓은 것.
지난 포스팅 막바지에 '쿼리 스트링'에 대해 짧게 설명했는데, URL 형식 API의 기본 요소가 '쿼리 스트링'이다.
API의 장점
1. 코드 재사용 - 이미 개발된 기능이나 서비스를 재사용할 수 있어서, 개발 시간과 비용을 절약하고 코드의 일관성과 신뢰성을 높일 수 있다.
2. 모듈화 - 각 컴포넌트가 자신만의 독립적인 역할을 가지며 서로 상호작용하므로, 전체 시스템 구조가 간결하고 관리하기 쉬워진다.
3. 플랫폼 독립성 - 하드웨어나 운영체제와 독립적으로 작동하므로, 다양한 환경에서 사용 가능하다.
4. 연계 및 통합 - 외부 서비스와 연계하여 새로운 기능이나 서비스를 만들거나, 기존 시스템과 통합하기 쉽다.
그러나 공개된 API는 보안 문제에 주의해야 한다.
API를 배우면서 함께 배운 개념이 '서버와 클라이언트'였다.
이건 내가 다른 포스팅에도 정리해뒀던 내용이라 간단하게 요약하자면,
프론트엔드(client)가 백엔드로 데이터를 요청(request)하면, 백엔드(server)가 프론트엔드에게 응답(response)한다.
REST API (REpresentational State Transfer API)
REST 아키텍쳐 스타일의 디자인 원칙을 준수하는 API로, 'RESTful API'라고도 부른다.
(REST API를 제공하는 웹사이트를 RESTful 하다고 말할 수 있음!)
HTTP 통신에서 CRUD 요청을 resource + method로 표현하여 전달한다.
즉, HTTP 메서드인 CRUD 방식을 사용해서 정보의 조회, 생성, 수정, 삭제를 간편하게 해준다.
CRUD한 결과를 받아올때 URL에 path가 생기고 쿼리가 생긴다.
URL 구조에 관해서는 다른 포스팅에 작성해뒀음!
- CRUD - Create, Read, Update, Delete
- resource - 특정 장소에 보관되어있는, 우리가 가져와야하는 데이터 자원
- method - CRUD 중에 데이터를 요청할 방식
※ REST API에 대한 자세한 설명 - https://www.ibm.com/kr-ko/topics/rest-apis
OPEN API
백엔드와 상호작용하지 않고, 공개된 서버에서 데이터를 불러오는 것.
사용법이 안내되어 있고, 보통 사용자 key를 발급받아야 사용할 수 있다.
데이터 요청 메소드fetch() - JS에서 기본적으로 제공하는 메소드.
ajax() - jQuery 메소드, CDN 필요함. 무겁다.
axois() - CDN 필요하고, 설치 필수
OPEN API 예시 코드를 작성해보기 전에, 알아야 하는 개념이 하나 더 있었다.👀
동기와 비동기
- 동기 (synchronous) - 코드가 위에서부터 순서대로 진행되는 것
- 비동기 (asynchronous) - 순서대로 진행되지 않고, 먼저 읽히면 먼저 진행되는 것
JS는 동기적인 언어지만, 특정 코드를 비동기적으로 실행되도록 만들 수도 있다.
예를 들어, 서버에서 데이터를 불러와서 화면에 보여줘야하는 경우, 데이터를 전부 가져온 이후에 화면에 보여주는 코드가 실행될 수 있게 코드실행 시간에 제한을 걸어서 비동기적으로 진행되도록 해야한다. 그렇지 않으면, 화면에 보여주는 코드가 데이터를 가져오기 전에 실행되어 error가 발생된다.
// 비동기코드 예시
console.log('1번');
setTimeout(() => {
console.log('2번');
}, 1000);
console.log('3번');
// setTimeout() 기본형태
setTimeout(()=>{실행될 코드}, 밀리초);
2번을 setTimeout() 내부에 작성했기 때문에, 1번과 3번이 먼저 실행되고 2번이 실행된다.
→ setTimeout()은 설정한 시간 이후에 첫번째 인자 내부의 동작이 실행되도록 하는 메소드.
비동기 코드를 동기적으로 처리하는 방법
1. 콜백함수(함수 내부의 인자로 들어있는 함수) 사용하기
아래 예시처럼 사용하면 되는데, 콜백함수만 사용한다면 코드가 엄청 길어져서 가독성이 떨어지고 유지/보수가 어렵다.
setTimeout(() => {
userId = 'layla01'; // 서버에서 받아온 id
if (id === userId) {
console.log('로그인 성공');
} else {
console.log('로그인 실패');
}
}, 1000);
2. promise
ES6에서 추가된 JS 문법으로 아래 예시 코드처럼 'new Promise'를 만들어서 사용한다.
→ new Promise가 만들어질 때, 내부의 executor(실행함수)가 자동으로 실행된다.
→ 매개변수로 (JS가 자체 제공하는 콜백인) resolve와 reject가 오고, 이러한 첫 줄의 구조는 언제나 똑같다.
→ 코드 실행된 이후, 작업의 완료 여부를 매개변수로 전달한다. 성공은 resolve, 실패는 reject.
→ 초기에는 state가 pending 상태이다가, 작업이 성공해서 resolve가 호출되면 fulfilled, 실패하면 rejected로 변화한다.
→ 완료된 작업 결과는 성공 시에는 .then(), 실패 시에는 .catch()로 처리한다. finally()는 결과 상관없이 무조건 실행.
const promise = new Promise((resolve, reject) => {
console.log('promise 시작');
setTimeout(() => {
userId = 'layla01'; // 서버에서 아이디 받아옴
if (userId === id) {
resolve(userId);
} else {
reject(new Error('아이디가 틀렸습니다'));
}
}, 2000);
});
promise
.then((data) => {
console.log(`userId는 ${data}입니다`); // 성공 시 보여줄 코드
})
.catch((error) => {
console.log(error); // 실패 시 보여줄 코드
})
.finally(() => {
console.log('promise 종료'); // 결과 상관없이 보여줄 코드
});
※ promise에 대한 자세한 설명 - https://ko.javascript.info/promise-basics
수업시간에 배운 내용은 아니지만 관련된 내용이라 여기에 추가.
.
promise.all
모든 프라미스가 성공해야 결과를 반환하고, 하나라도 실패하면 즉시 실패 → 어떤 작업이 실패한건지 알 수 없다. 여러 api 요청을 동시에 보내고 모든 요청이 성공적으로 완료되어야만 다음 작업을 진행해야 할 때 사용한다.
ex) 마이페이지가 제대로 보여지기 위해 사용자 정보와 사진 등 모든 api 요청이 성공적으로 완료되어야 페이지 표시 가능
.
promise.allSettled
모든 프라미스의 완료를 기다렸다가 각 프라미스의 결과를 배열로 반환 → 어떤 작업이 실패했는지 알 수 있다. 여러 비동기 작업을 동시에 실행하지만 각 작업의 성공/실패 여부가 전체 흐름에 큰 영향을 미치지 않을 때 사용한다.
ex) 여러 이미지 업로드중 일부만 업로드에 실패했을 때, 실패한 이미지만 재업로드할 수 있어 효율적이다.
3. async & await
promise 역시 결과의 처리 방식을 chaining 하다보면 꼬리를 물게 되어 코드의 가독성이 떨어질 수 있다.
async & await는 promise를 다르게 사용하는 방법으로, 더욱 직관적으로 코드를 작성할 수 있다.
- async - 해당 함수 내부에 await를 사용할 것임을 알린다. async가 붙은 함수는 항상 promise를 반환한다.
- await - 해당 함수가 실행될 때까지 코드를 기다리도록 한다.
// promise를 함수에 담아, 해당 함수 호출시 promise 실행되도록 작성
const promiseFunc = () => {
return new Promise((res, rej) => {
console.log('promise 시작');
// 서버에서 데이터 받아오는 시간
setTimeout(() => {
userId = 'layla01';
if (userId === id) {
res(userId);
} else {
rej(new Error('아이디가 틀렸습니다'));
}
}, 2000);
});
};
const checkUserId = async () => {
try {
const result = await promiseFunc();
console.log(result);
} catch (error) {
console.log(error);
} finally {
console.log('promise 종료');
}
};
checkUserId();
※ async & await 설명 - https://ko.javascript.info/async-await
fetch()
(ES6부터 등장한) 브라우저와 서버 통신을 위한 메소드.
promise 기반이라 리턴값으로 promise 객체를 반환한다.
// OPEN API 사용하여 고양이 사진 가져오기
fetch('https://api.thecatapi.com/v1/images/search')
.then((response) => response.json())
.then((data) => {
const img = document.createElement('img');
img.src = data[0].url;
img.setAttribute('width', 500);
document.querySelector('#cat').append(img);
});
JSON (JavaScript Object Notation)
데이터를 전송하기 위한 경량의 데이터 포맷.
여러 언어에서 쉽게 사용할 수 있어 서버와의 통신에서는 JSON으로 통신한다.
JS 객체와 유사하지만, key도 따옴표로 감싼다는 차이가 있다.
// json의 기본 형태
{
"type" : "panda",
"name" : "fubao",
"birth" : 2020,
"gender" : "female",
"address" : {
"city" : "Yong-in",
"zoo" : "Everland"
}
}
// json을 js 객체로 파싱하기 (parse)
const obj = JSON.parse(jsonString);
console.log(obj);
console.log(obj.name);
// js 개체를 json으로 파싱하기 (stringify)
const jsonStr = JSON.stringify(obj);
console.log(jsonStr);
여기까지 배운 이후, 리더님과 함께 'JSONplaceholder'라는 OPEN API를 이용해서 CRUD 작업을 해보았다.
// get 요청: 특정 게시물을 가져오는 함수 (get은 default이기 때문에 method 생략 가능)
const getPost = () => {
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((res) => res.json())
.then((data) => {
console.log(data);
});
};
// post 요청: 새로운 게시물 생성하는 함수
const postPost = () => {
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify({
title: '제목 작성',
body: '게시물 내용',
userId: '1',
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
// json으로 불러온다는 의미
},
})
.then((response) => response.json())
.then((data) =>
// 브라우저에 렌더링 등 응답으로 받은 데이터를 사용
console.log(data)
)
.catch((err) => {
// 에러 발생시 원하는 처리방법
console.log('error:', err);
});
};
// delete 요청
const deletePost = () => {
fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'DELETE',
})
.then((response) => response.json())
.then((data) => console.log('delete!'))
.catch((err) => console.log('del err:', err));
};
함수 내보내기 & 불러오기
코드를 작성할 때 가장 중요한 것은 반복을 줄이는 일이다. 이건 SeSAC 수업을 듣기 전에 혼자 공부하면서도 들었던 말이고, 현재 수업에서도 여러 차례 강조되었던 내용이다.🙂 많은 곳에서 반복 사용될 함수에 내보내기 & 불러오기 기능을 적용하면, 불필요한 코드의 반복을 줄여서 유지/보수가 쉬워질뿐 아니라 효율적인 작업도 가능하다.
// 함수 내보내는 방법
// 1. 함수 앞에 export 키워드 사용
export function consoleName(name) {
console.log(`당신의 이름은 ${name}입니다`);
}
export function consoleName2(name) {
console.log(`당신의 이름은 ${name}입니다 2`);
}
// default 키워드 작성시 해당 함수가 파일의 대표 함수가 된다.
export default function consoleNameMain(name) {
console.log(`당신의 이름은 ${name}인가? main 함수`);
}
// 2. 마지막에 모아서 export문 작성해서 내보내기 가능
export function consoleName(name) {
console.log(`당신의 이름은 ${name}입니다`);
}
export function consoleName2(name) {
console.log(`당신의 이름은 ${name}입니다 2`);
}
export { consoleName, consoleName2 };
함수 내보내기는 export 키워드를 사용한다. 이는 객체, 원시값 모두 동일하다.
내보낸 함수 불러오기는 import 키워드를 사용한다.
- export 키워드만 작성한 경우에는 첫번째 예시처럼 중괄호에 감싸서 작성한다.
- export default 키워드로 작성했다면 중괄호 없이 불러온다.
// 함수 불러오기
import { consoleName, consoleName2 } from './02_util.js';
consoleName('layla');
consoleName2('a');
import consoleNameMain from './02_util.js';
consoleNameMain('b');
// 한 번에 여럿 불러오기
import consoleNameMain, { consoleName, consoleName2 } from './02_util.js';
consoleName('layla');
consoleName2('a');
consoleNameMain('b');
여기까지가 이날 배운 내용이었다. 수업 시간에 OMDB라는 OPEN API로 영화 목록을 불러와서 브라우저에 출력하는 예시를 같이 해봤는데, 내가 영화를 아주 좋아해서 제일 흥미로웠던 예시였다.🎬 배운 내용을 복습해볼겸, 주말동안 OMDB API로 가상의 OTT 사이트를 만들어볼까?🤔