ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 24.06.10 TIL 동기와비동기/Promise/HTTP/json-server/axios
    sparta 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
Designed by Tistory.