React의 비동기 통신

2024. 3. 17. 19:51E8

자바스크립트 비동기 1

동기 vs 비동기

비동기 응답을 받으면, 응답을 처리하는 callback 함수를 task queue에 넣음

event loop는 main thread에 여유가 있을 때 task queue에서 함수를 꺼내 실행

// 동기 스레드 제어권 안넘기고 순서대로 실행
console.log("This is synchronous...");
for (let i = 0; i < 1000000000; ++i) {
  console.log("I am blocking the main thread...");
}
console.log("This is synchronous...DONE!");
// 비동기
setTimeout(() => console.log("This is asynchronous..."), 5000); // 5초 뒤에 실행 x 여유 있을 때
console.log("This is synchronous...");
for (let i = 0; i < 1000000000; ++i) {
  console.log("I am blocking the main thread...");
}

비동기 처리 코드를 감싼 블록은 task queue에 넣어짐

main thread가 동기 코드를 실행한 후에 제어권이 돌아왔을 때 event loop가 task queue에 넣어진 비동기 코드를 실행

// 비동기 예시
request("user-data", (userData) => {
  console.log("userData로드");
  saveUsers(userData);
});
console.log("DOM 변경");
console.log("유저입력");

자바스크립트 비동기 2

비동기 처리 위한 내부 구조

async/await

Promise 체인을 구축하지 않고도, Promise를 직관적으로 사용

async function fetchUserWithAddress(id) {
  try {
    const user = await request(`/user/${user.id}`);
	if (!user) throw newError("No user found.");
	const address = await request(`/user/${user.id}/address`);
	if (!address.userId !== user.id) throw new Error("No address match with user.");
    return { ...user, address };
  } catch (e) {
    console.log("error : ", e);
  }
}
async function fetchUserWithAddress(id) {
  let user = null;
  try {
    user = await request(`/user/${user.id}`);
  } catch (e) {
    console.log("User fetch error: ", e);
    return;
  }
  try {
    const address = await request(`/user/${user.id}/address`);
    return { ...user, address };
  } catch (e) {
    console.log("Address fetch error: ", e);
  }
}

Promise.all은, 특정 비동기 작업이 상대적으로 빠르게 끝나도, 느린 처리를 끝까지 기다려야만 함

이와 달리, async/await을 활용할 경우 parallelism을 구현할 수 있음. 즉, 끝난 대로 먼저 처리될 수 있음

 

async/await -Promise와의 조합

async function fetchUserWithAddress(id) {
  return await Promise.all([
    (async () => await request(`/users/${id}`))(),
    (async () => await request(`/users/${id}/address`))(),
  ]);
}
fetchUserWithAddress("1234")
  .then(([user, address]) => ({ ...user, address }))
  .catch((e) => console.log("Error : ", e));

 

CORS (Cross-Origin Resource Sharing)

브라우저는 모든 요청 시 Origin 헤더를 포함

서버는 Origin 헤더를 보고, 해당 요청이 원하는 도메인에서부터 출발한 것인지를 판단

다른 Origin에서 온 요청은 서버에서 기본적으로 거부

 

보통 서버의 endpoint와 홈페이지 domain은 다른 경우가 많음

따라서 서버에서는 홈페이지 domain을 허용하여, 다른 domain이라 하더라도 요청을 보낼 수 있게 함

Access-Control-* 을 포함하는 헤더에 CORS 관련 정보를 클라이언트로 보냄

 

웹사이트에 악성 script가 로드되어, 수상한 요청을 하는 것을 막기 위함

반대로, 익명 유저로부터의 DDos 공격 등을 막기 위함

서버에 직접 CORS 설정을 할 수 없다면, Proxy 서버 등을 만들어 해결


유저 데이터를 비동기로 요청해 렌더링

유저 정보 로딩 시 다르게 표시

// 데이터가 로딩 중인 경우 유저 정보를 불러오고 있다는 안내문을 띄움
export default function BitcoinApp() {
  const [users,setUsers]=useState(undefined)
  useEffect(()=>{
      getUsers().then(setUsers)
  },[]) // getUser로 데이터 가져와 등록
  return (
    <div>
    {! users ? (<div>유저 정보를 불러오는 중입니다.</div>): users.map(user =>(
        <WrappedUserDetail 
        email={user.email}
        bitcoinAddress={user.bitcoinAddress}
        bitcoinBalance={user.bitcoinBalance}
      />
    ))}
    </div>
  );
}

자식에서 부모로 보낸 걸 또 다른 자식으로 건네기

import React, { useState, useEffect } from "react";
import * as authAPI from "../service/auth";
import styled from "styled-components";
import UserDetail from "./UserDetail";
import RegisterForm from "./RegisterForm"
// RegisterForm을 이용해 유저 정보를 가져와 화면을 업데이트하세요.

export default function BitcoinApp() {
  const [users, setUsers] = useState(undefined);
  const handleSumbit=(formData)=>{ authAPI.registerUser(formData).then(authAPI.getUsers).then(setUsers).catch(console.error) }
  // 등록하고 다시 유저 정보 가져와서 셋팅
  useEffect(() => {
    authAPI.getUsers().then((data) => {
      console.log(data);
      setUsers(data);
    });
  }, []);
  if (!users) return <div>유저 정보를 불러오는 중입니다...</div>;
  return (
    <div>
    <RegisterForm onSubmit={handleSumbit} />
      {users.map((user) => ( <WrappedUserDetail {...user} /> ))}
    </div>
  );
}
import React, { useRef } from "react";
import styled from "styled-components";

export default function RegisterForm({ className, onSubmit }) {
  const formRef = useRef();
  const emailRef = useRef();
  const passwordRef = useRef();
  const confirmPasswordRef = useRef();

  const submitForm = (e) => {
    e.preventDefault();
    const email = emailRef.current.value;
    const password = passwordRef.current.value;
    const confirmPassword = confirmPasswordRef.current.value;
    if (email.length === 0 || password.length === 0) return;
    if (confirmPassword !== password) {
      confirmPasswordRef.current.setCustomValidity("Different from password");
      return;
    }

    const formData = { email, password };
    onSubmit(formData);
    formRef.current.reset();
  };
  return (
    <Container className={className}>
      <form ref={formRef}>
        <fieldset>
          <label htmlFor="email">Email</label>
          <input placeholder="Enter email." required ref={emailRef} id="email" type="email" name="email" autocomplete="off" />
        </fieldset>
        <fieldset>
          <label htmlFor="password">Password</label>
          <input required ref={passwordRef} id="password" type="password" name="password" placeholder="Enter password." />
        </fieldset>
        <fieldset>
          <label htmlFor="confirmPassword">Confirm Password</label>
          <input required ref={confirmPasswordRef} id="confirmPassword" type="password" name="confirmPassword" placeholder="Enter password again." />
        </fieldset>
        <RegisterButton onClick={submitForm}>Register</RegisterButton>
      </form>
    </Container>
  );
}

 

페이지 이동 중 로딩을 callback으로 구현

버튼을 클릭하기 전에는 Nothing
버튼을 클릭한 후 2초 동안 Loading...
버튼을 클릭한 후 2초 이후에 HomePage

export default function HomePage() {
  const [isLoading, setIsLoading] = useState(false);
  const [isLoaded, setIsLoaded] = useState(false);
  const handleClick = () => {
    setIsLoading(true);
    setTimeout(() => {
      setIsLoading(false);
      setIsLoaded(true);
    }, 2000);
  }
  return(
    <div className="homepage">
      {/* 조건부 렌더링을 통해 출력될 문구를 정의해주세요. */}
      {isLoaded ? "HomePage" : isLoading ? "Loading..." : "Nothing"}
      <button onClick={handleClick}>
        불러오기
      </button>
    </div>
  );
}

#코딩독학 #코딩인강 #코딩배우기 #개발자 #코딩이란 #코딩교육
#프론트엔드부트캠프 #백엔드부트캠프 #국비지원부트캠프 #개발자 #백엔드 #AI부트캠프 #개발자국비지원 #백엔드개발자 #프론트엔드개발자

'E8' 카테고리의 다른 글

REDUX  (1) 2024.03.23
상태 관리  (0) 2024.03.20
SPA 라우팅  (0) 2024.03.16
React 스타일링  (0) 2024.03.13
react 코드 응용  (0) 2024.03.08