-
24.06.10 TIL 동기와비동기/Promise/HTTP/json-server/axiossparta TIL 2024. 6. 10. 19:19
1. 동기와 비동기
- 동기 : 순차적으로 진행(순서가 될때까지 다른작업은 대기상태)되어 비효율적 이지만,
데이터를 받아온 후 보여주는 등의 순서가 중요한 작업에서는 동기처리가 맞다.
- 비동기 : 비순차적으로 처리가능한 것들을 먼저 진행, 순서가 중요하지 않은 작업에 유용
function sendTextMessage(message, callback) { // 메시지를 보내는 비동기 작업을 가정해요. setTimeout(() => { console.log("문자 메시지 전송 완료: " + message); callback(); // 응답이 도착하면 콜백 함수 호출 }, 2000); // 2초 후에 메시지 전송이 완료된다고 가정 } console.log("메시지 보내기 시작"); sendTextMessage("안녕, 잘 지내?", function () { console.log("메시지 전송 후 후속 작업 수행"); }); console.log("다른 작업 수행"); // 출력 결과: // 메시지 보내기 시작 // 다른 작업 수행 // 문자 메시지 전송 완료: 안녕, 잘 지내? // 메시지 전송 후 후속 작업 수행
▲ 비동기 예
console.log("첫 번째"); for (let i = 0; i < 1000000000; i++) { // 무거운 작업 } console.log("두 번째");
▲ for문은 동기적으로 처리된다. for루프가 끝나야 '두번째'가 출력 됨
import React, { useState, useEffect } from "react"; function App() { const [post, setPost] = useState(null); useEffect(() => { console.log("시작"); fetch("https://jsonplaceholder.typicode.com/posts/1") .then((response) => response.json()) .then((json) => { setPost(json); console.log("데이터 가져오기 완료"); }); console.log("끝"); }, []); return <div>{post ? <div>{post.title}</div> : <div>Loading...</div>}</div>; } export default App;
▲ fetch는 비동기적처리 동기적처리는 async/await으로 쓰면 된다.
2. Promise?
자바스크립트에서 비동기 작업의 완료 또는 실패를 처리하기 위해 사용되는 개념으로,
비동기 작업이 성공하거나 실패했을 때 각각의 결과를 처리하는 데 도움을 준다. (.then/.catch/finaly)
비동기 작업의 결과를 다루기 쉽게 하기 위해 만들어졌으며, 비동기 작업이 끝난 이후에 실행될 콜백을 등록할 수 있는 메서드를 제공한다.
Promise 객체를 생성하기 위해 Promise 생성자를 사용할 수 있지만, 사용할 일이 많지는 않다.
doSomething(() => { doSomethingElse(() => { doThirdThing(() => { console.log("모든 작업 완료"); }); }); });
▲ PromisePromise가 없다면 : 코드가 복잡해지고 가독성이 떨어져 유지보수가 어려운, 위와 같은 콜백 지옥을 경험 해야만 한다.
Promise의 세 가지 상태 - 대기(Pending): 초기 상태(이행되거나 거부되지 않은 상태) resolve나 reject로 인해 다른 상태로 변경되기 전까지의 상태를 말함
- 이행(Fulfilled): 비동기 작업이 성공적으로 완료된 상태. resolve로 인해 pending 상태에서 fulfilled상태로 변경
- 거부(Rejected): 비동기 작업이 실패한 상태. reject로 인해 pending 상태에서 rejected 상태로 변경
✔️ Promise 객체는 then, catch, finally 메서드를 통해 이행되거나 거부된 이후의 동작을 정의할 수 있다.
// (1) 컴포넌트가 마운트될 때 fetch를 사용해 데이터를 비동기적으로 가져와요 // (2) Promise의 then 메서드를 사용해 데이터를 설정합니다. // (3) 에러가 발생하면 catch 메서드를 통해 에러를 처리합니다. import React, { useState, useEffect } from "react"; function App() { const [post, setPost] = useState(null); useEffect(() => { fetch("https://jsonplaceholder.typicode.com/posts/1") .then((response) => response.json()) .then((json) => setPost(json)) .catch((error) => console.error("데이터 펫칭 오류! => ", error)); }, []); return <div>{post ? <div>{post.title}</div> : <div>Loading...</div>}</div>; } export default App;
비동기 작업을 병렬로 처리하기
Promise.all 메서드를 사용하면 여러 비동기 작업을 병렬로 처리할 수 있다. 모든 작업이 완료될 때까지 기다린 후, 결과를 한 번에 처리 !
// 두 개의 API 호출을 병렬로 처리하는 예제 // 모든 비동기 작업이 완료되면 데이터를 설정합니다. import React, { useState, useEffect } from "react"; function App() { const [data, setData] = useState({ posts: [], users: [] }); useEffect(() => { // 두 개의 처리를 동시에 시작해요. Promise.all([ fetch("https://jsonplaceholder.typicode.com/posts").then((response) => response.json() ), fetch("https://jsonplaceholder.typicode.com/users").then((response) => response.json() ), ]) .then(([posts, users]) => { setData({ posts, users }); }) .catch((error) => console.error("데이터 펫칭 오류! => ", error)); }, []); return ( <div> <h1>Posts and Users Data</h1> <div> <h2>Posts</h2> {data.posts.length > 0 ? ( data.posts.map((post) => <div key={post.id}>{post.title}</div>) ) : ( <div>Loading posts...</div> )} </div> <div> <h2>Users</h2> {data.users.length > 0 ? ( data.users.map((user) => <div key={user.id}>{user.name}</div>) ) : ( <div>Loading users...</div> )} </div> </div> ); } export default App;
⭐️ Promise객체를 async/await 으로 동기처리하여 사용하기
async와 await 키워드는 Promise 객체를 더욱 간편하게 다룰 수 있도록 도와준다.
async 함수는 항상 Promise를 반환, await 키워드는 Promise가 이행될 때까지 기다림
이 방법을 사용하면 비동기 코드를 더 동기 코드처럼 작성할 수 있어 가독성이 향상된다 !
async : 함수 내부에서 명시적으로 return 문으로 값을 반환하면 자동으로 Promise.resolve()를 통해 감싸져서 반환
await : Promise가 이행될 때까지 기다리고, 이행된 Promise의 결과를 반환한다. await는 async 함수 내부에서 사용
const testFunc = async () => { return 1; }; const value = testFunc(); console.log("async/await text => ", value);
왜 async/await를 사용해야 하나?
- 가독성: async / await 구문을 사용하면 비동기 코드를 동기 코드처럼 작성할 수 있어 가독성이 크게 향상된다
- 에러 처리: try...catch 구문을 사용하여 비동기 작업에서 발생하는 오류를 간편하게 처리할 수 있다.
- 코드의 간결함: 콜백 지옥이나 체이닝을 피할 수 있어 코드가 간결해진다.
import React, { useState, useEffect } from "react"; function App() { const [post, setPost] = useState(null); useEffect(() => { const fetchPost = async () => { try { const response = await fetch( "https://jsonplaceholder.typicode.com/posts/1" ); const data = await response.json(); setPost(data); } catch (error) { console.error("Error fetching post:", error); } }; fetchPost(); }, []); return <div>{post ? <div>{post.title}</div> : <div>Loading...</div>}</div>; } export default App;
3. HTTP ?
HTTP(HyperText Transfer Protocol)는 웹 상에서 데이터를 주고받기 위한 프로토콜 통신규약
클라이언트와 서버 간의 요청과 응답을 정의하며, 상태코드와 헤더를 포함한 다양한 요소를 가지고 있다.
데이터 주고받을 약속~🤙🏻 - 무상태성: HTTP는 상태를 유지하지 않음. 각 요청은 독립적이며, 이전 요청의 정보를 기억하지 않기 때문에 무상태(stateless)라고 한다.
- 확장성: HTTP는 다양한 확장 헤더를 추가하여 기능을 확장할 수 있다.
- 유연성: HTTP는 다양한 데이터 형식을 전송할 수 있다. (텍스트, 이미지, 비디오 등)
GET /index.html HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 Accept: text/html
HTTP 메서드는 클라이언트가 서버에게 요청할때 사용한다.
REST API는 이러한 HTTP 메서드를 사용하여 CRUD 작업을 수행함
API 명세서 ⭐️ RESTful 원칙에 따라 작성된 API 명세서
React에서 HTTP 요청을 보내는 일반적인 방법은 fetch API 또는 axios 라이브러리를 사용하는 것
▼ fetch API를 사용하는 예시 ( axios 라이브러리는 이어지는 강의에서 )
// 데이터를 가져오기 위한 GET 요청 !!!!! import React, { useState, useEffect } from "react"; function App() { const [data, setData] = useState(null); useEffect(() => { const fetchData = async () => { try { // get 요청 시, fetch는 method를 명시하지 않아도 돼요. const response = await fetch( "https://jsonplaceholder.typicode.com/posts/1" ); const result = await response.json(); setData(result); } catch (error) { console.error("Error fetching data:", error); } }; fetchData(); }, []); return <div>{data ? <div>{data.title}</div> : <div>Loading...</div>}</div>; } export default App;
데이터를 생성하기 위한 POST 요청 !!!!! import React, { useState } from "react"; function App() { const [title, setTitle] = useState(""); const [body, setBody] = useState(""); const [response, setResponse] = useState(null); const handleSubmit = async (event) => { event.preventDefault(); try { const res = await fetch("https://jsonplaceholder.typicode.com/posts", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ title: title, body: body, userId: 1, }), }); const result = await res.json(); setResponse(result); } catch (error) { console.error("Error creating data:", error); } }; return ( <div> <form onSubmit={handleSubmit}> <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Title" /> <textarea value={body} onChange={(e) => setBody(e.target.value)} placeholder="Body" /> <button type="submit">Create Post</button> </form> {response && <div>Created Post ID: {response.id}</div>} </div> ); } export default App;
⭐️ json-server
json-server란 무엇이며, 왜 사용하나요?
아주 간단한 ‘DB와 API 서버를 생성해주는 패키지’이며,
Backend에서 실제 DB와 API Server가 구축될 때까지 Frontend개발에 임시적으로 사용할 mock data를 생성하기 위함
json-server를 통해 FE는 BE가 하고 있는 작업을 기다리지 않고, FE의 로직과 화면을 구현 할 수 있어 효율적으로 협업을 할 수 있다.
yarn add json-server
yarn add json-server -D // 개발 환경인 경우, -D 옵션을 함께 입력1. json-server 실행하기
루트경로에 db.json 파일 추가
{ "todos": [] }
yarn json-server db.json --port 4000
터미널에 실행 하면,
왕귀여운 이모티콘 등장 😊 복사해서 링크 복사해서 입력하면 값이 짜잔
axios ?
node.js와 브라우저를 위한 Promise 기반 http 클라이언트 라고 소개하고 있다.
다시 말해 http를 이용해서 서버와 통신하기 위해 사용하는 패키지라고 생각하면 됨
yarn add axios
1. json-server 설정
1. get
// src/App.js import React, { useEffect, useState } from "react"; import axios from "axios"; // axios import 합니다. const App = () => { const [todos, setTodos] = useState(null); // axios를 통해서 get 요청을 하는 함수를 생성합니다. // 비동기처리를 해야하므로 async/await 구문을 통해서 처리합니다. const fetchTodos = async () => { const { data } = await axios.get("http://localhost:4000/todos"); setTodos(data); // 서버로부터 fetching한 데이터를 useState의 state로 set 합니다. }; // 생성한 함수를 컴포넌트가 mount된 후 실행하기 위해 useEffect를 사용합니다. useEffect(() => { // effect 구문에 생성한 함수를 넣어 실행합니다. fetchTodos(); }, []); // data fetching이 정상적으로 되었는지 콘솔을 통해 확인합니다. console.log(todos); return <div>App</div>; }; export default App;
위 코드 입력시 import { useState, useEffect } from "react"; import axios from "axios"; const App = () => { const [todos, setTodos] = useState(null); const [todo, setTodo] = useState({ title: "", }); useEffect(() => { const fetchPost = async () => { try { const { data } = await axios.get("http://localhost:4000/todos"); setTodos(data); } catch (error) { console.error("Error:", error); } }; fetchPost(); }, []); console.log("post", todos); const onSubmitHandler = async (todo) => { await axios.post("http://localhost:4000/todos", todo); }; return ( <div> <h3>axios연습</h3> <form onSubmit={(e) => { e.preventDefault(); onSubmitHandler(todo); }} > <input type="text" onChange={(e) => { setTodo({ ...todo, title: e.target.value }); }} /> <button type="submit">추가하기</button> </form> {todos && ( <ul> {todos.map((item) => ( <li key={item.id}>{item.title}</li> ))} </ul> )} </div> ); }; export default App;
위 코드 입력시 _ 추가하기 한 리스트가 json에 저장된다 ! 2. POST
import { useState, useEffect } from "react"; import axios from "axios"; const App = () => { const [todos, setTodos] = useState(null); const [todo, setTodo] = useState({ title: "", }); useEffect(() => { const fetchPost = async () => { try { const { data } = await axios.get("http://localhost:4000/todos"); setTodos(data); } catch (error) { console.error("Error:", error); } }; fetchPost(); }, []); console.log("post", todos); const onSubmitHandler = async (todo) => { const { data } = await axios.post("http://localhost:4000/todos", todo); setTodos([...todos, data]); }; return ( <div> <h3>axios연습</h3> <form onSubmit={(e) => { e.preventDefault(); onSubmitHandler(todo); }} > <input type="text" onChange={(e) => { setTodo({ ...todo, title: e.target.value }); }} /> <button type="submit">추가하기</button> </form> {todos && ( <ul> {todos.map((todo) => ( <li key={todo.id}>{todo.title}</li> ))} </ul> )} </div> ); }; export default App;
페이로드(내가보낸요청), 응답, 헤더를 보면 다 알수있다 3. DELETE
import { useState, useEffect } from "react"; import axios from "axios"; const App = () => { const [todos, setTodos] = useState([]); const [todo, setTodo] = useState({ title: "", }); useEffect(() => { const fetchPost = async () => { try { const { data } = await axios.get("http://localhost:4000/todos"); setTodos(data); } catch (error) { console.error("Error:", error); } }; fetchPost(); }, []); console.log("post", todos); const onSubmitHandler = async (todo) => { const { data } = await axios.post("http://localhost:4000/todos", todo); setTodos([...todos, data]); }; const onDeleteHandler = async (id) => { await axios.delete(`http://localhost:4000/todos/${id}`); setTodos(todos.filter((todo) => todo.id !== id)); }; return ( <div> <h3>axios연습</h3> <form onSubmit={(e) => { e.preventDefault(); onSubmitHandler(todo); }} > <input type="text" value={todo.title} onChange={(e) => { setTodo({ ...todo, title: e.target.value }); }} /> <button type="submit">추가하기</button> </form> {todos && todos.map((todo) => ( <div key={todo.id}> <span>{todo.title}</span>{" "} <button onClick={() => onDeleteHandler(todo.id)}>삭제</button> </div> ))} </div> ); }; export default App;
4. PATCH (수정), (PUSH는 대체)
import { useState, useEffect } from "react"; import axios from "axios"; const App = () => { const [todos, setTodos] = useState([]); const [todo, setTodo] = useState({ title: "", }); const [targetId, setTargetId] = useState(""); const [editTodo, setEditTodo] = useState({ title: "" }); useEffect(() => { const fetchPost = async () => { try { const { data } = await axios.get("http://localhost:4000/todos"); setTodos(data); } catch (error) { console.error("Error:", error); } }; fetchPost(); }, []); console.log("post", todos); const onSubmitHandler = async (todo) => { const { data } = await axios.post("http://localhost:4000/todos", todo); setTodos([...todos, data]); }; const onDeleteHandler = async (id) => { await axios.delete(`http://localhost:4000/todos/${id}`); setTodos(todos.filter((todo) => todo.id !== id)); }; const onEditHandler = async (targetId, editTodo) => { const { data } = await axios.patch( `http://localhost:4000/todos/${targetId}`, editTodo ); setTodos(todos.map((todo) => (todo.id === targetId ? data : todo))); }; return ( <div> <h3>axios연습</h3> <form onSubmit={(e) => { e.preventDefault(); onSubmitHandler(todo); }} > <div> <input type="text" placeholder="수정할 ID" onChange={(e) => { setTargetId(e.target.value); }} /> <input type="text" placeholder="수정할 내용" onChange={(e) => { setEditTodo({ ...editTodo, title: e.target.value }); }} /> <button type="button" onClick={() => onEditHandler(targetId, editTodo)} > 수정 </button> </div> <input type="text" value={todo.title} onChange={(e) => { setTodo({ ...todo, title: e.target.value }); }} /> <button type="submit">추가하기</button> </form> {todos && todos.map((todo) => ( <div key={todo.id}> <span>{todo.title}</span>{" "} <button onClick={() => onDeleteHandler(todo.id)}>삭제</button> </div> ))} </div> ); }; export default App;
fetch와 axios는 모두 HTTP 요청(GET, POST …)을 처리하기 위한 JavaScript 라이브러리인데,
다음과 같은 axios의 장점 때문에 React에서 axios를 fetch보다 선호하는 경우가 있다.
1. 기본 설정 및 인터셉터 지원
✔️ 기본 설정: axios는 기본 설정을 정의하고, 이를 통해 모든 요청에 공통 설정을 적용할 수 있다.
const axiosInstance = axios.create({ baseURL: 'https://api.example.com', timeout: 1000, headers: { 'X-Custom-Header': 'foobar' } });
✔️ 인터셉터: 요청 또는 응답을 가로채서 전처리 또는 후처리할 수 있다. 이를 통해 인증 토큰을 자동으로 추가하거나 오류를 일괄 처리할 수 있다.
axios.interceptors.request.use(config => { config.headers.Authorization = `Bearer ${token}`; return config; }, error => { return Promise.reject(error); });
2. 더 나은 오류 처리
에러 핸들링: axios는 HTTP 상태 코드를 기준으로 한 일관된 오류 처리를 제공한다.
fetch는 기본적으로 네트워크 오류만 catch 블록으로 전달되고, 4xx, 5xx 오류는 then 블록에서 처리해야 함
axios.get('/user/12345') .then(response => console.log(response.data)) .catch(error => { if (error.response) { // 서버가 4xx, 5xx 응답을 반환 console.log(error.response.data); console.log(error.response.status); console.log(error.response.headers); } else if (error.request) { // 요청이 전송되었지만 응답이 없음 console.log(error.request); } else { // 요청 설정 중에 발생한 오류 console.log('Error', error.message); } });
3. 브라우저 호환성
구형 브라우저 지원: axios는 구형 브라우저와의 호환성이 좋다. fetch는 구형 브라우저에서 지원되지 않을 수 있어 폴리필이 필요
4. 간단한 사용법
간결하고 직관적인 문법을 제공하여 사용하기 쉬움. fetch보다 코드가 더 깔끔해질 수 있다.
axios 2 - custom instance, interceptors
1. custom instance 만들기
1. axios폴더 - customAPI.js파일 생성
2. .create()로 baseURL설정
// App.jsx import { useState, useEffect } from "react"; // import axios from "axios"; import api from "./axios/api"; // 내가만든 customAPI import useEffect(() => { const fetchPost = async () => { try { const { data } = await api.get("/todos"); setTodos(data); } catch (error) { console.error("Error:", error); } }; fetchPost(); }, []); console.log("post", todos); const onSubmitHandler = async (todo) => { const { data } = await api.post("/todos", todo); setTodos([...todos, data]); }; const onDeleteHandler = async (id) => { await api.delete(`/todos/${id}`); setTodos(todos.filter((todo) => todo.id !== id)); }; const onEditHandler = async (targetId, editTodo) => { const { data } = await api.patch(`/todos/${targetId}`, editTodo); setTodos(todos.map((todo) => (todo.id === targetId ? data : todo))); }; // axios.get/post/delete/patch 를 api.get/post/delete/patch 수정 // 기존 url주소들 지우고 엔드포인트만 넣어줘도 똑같이 통신 됨.
2. interceptors
인터셉트 성공 인터셉트 실패
js베이직분반 : Redux, useContext
'sparta TIL' 카테고리의 다른 글
24.06.14 TIL (0) 2024.06.15 24.06.11 TIL TanStack Query (0) 2024.06.11 SpartaHub 리팩토링중 1 (0) 2024.06.09 24.06.03 TIL (0) 2024.06.03 24.06.01 TIL (0) 2024.06.02