React | Promise & async/await 그리고 API

Jihun Kim·2021년 8월 13일
3

리액트

목록 보기
2/3
post-thumbnail

Prmmise와 async/await

Promise

  • Promise란 비동기 처리에서 사용되는 객체로, promise가 상태를 관리하여 다른 코드가 비동기적으로 실행될 수 있도록 만드는 객체이다.
    👉 axios 또한 promise를 기반으로 만들어짐

async/await

  • '.then' 문법에 의해 생기는 '콜백지옥'을 극복하기 위해 만들어진 문법으로, async는 함수 이름의 제일 앞에, await는 결과를 기다릴 함수 앞에 작성한다.
    👉 async는 함수에서 비동기 처리를 위한 promise 동작을 한다는 것을 명시한다.
    👉 await은 호출되는 함수가 적절한 결과를 반환할 때까지 기다리도록 동작한다.
function resolveAfter2Seconds() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("resolved");
    }, 2000);
  });
}

async function asyncCall() {
  console.log("calling");
  const result = await resolveAfter2Seconds();
  console.log(result);
}

API 호출

🔥 호출 과정

  • useState를 이용해 사용자 리스트를 상태로 관리하고, useEffect를 이용해 API를 불러온다.
  • useEffect는 함수를 반환해야 하는데 async/await은 Promise 객체를 리턴하기 때문에 사용할 수 없다.
    👉 따라서, useEffect 안에서 async/await 함수를 선언하고 그 안에 setState()를 이용해 state을 관리해야 한다.
import React, { useState, useEffect } from 'react';
import axios from 'axios';

function Users() {
    // state인 users를 useState()로 선언하기
    const [users, setUsers] = useState([]);
    
    // async와 await를 이용한 useEffect()를 선언하기
    useEffect(()=>{
        async function fetch() {
            const response = await axios.get('<URL>');
        // 일단 response의 형태를 확인하고
        console.log(response.data);
        // fetch 함수 아래에 setUsers를 해주어야 한다.
        setUsers(response.data);
        };
        fetch();

    }, [])
    
    // users 출력
    const userName = users.map(
        (user) => (<li key={user.id}> {user.name} </li>)
    );

    return (
        <>
            <h4>사용자 리스트</h4>
            <div> {userName} </div>
        </>
    );
}

export default Users;

API 호출시 에러 핸들링

  • try {} catch (e) {} 형식으로 사용한다.
    👉 try 안에는 async/await 구문이 들어가며, catch 안에는 에러를 핸들링할 내용이 들어간다.
import React, { useState, useEffect } from 'react';
import axios from 'axios';

function Users() {
    const [users, setUsers] = useState([]);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        async function fetchUser() {
            // try ~ catch를 이용해 예외 처리를 하세요.
            try {
                const response = await axios.get(
                    'https://jsonplaceholder.typicode.com/error'
                );
                setUsers(response.data);
            } catch(e) {
                setError(e);
            }
            
        };
        fetchUser();
    }, []);
    
 if (error) return <h4>에러 발생!</h4>;
 ...

버튼으로 API 호출하기

  • '다시 불러오기' 버튼을 클릭하면 fetchUser()가 실행되고, 그러면 재렌더링 되기 때문에 다시 mount 되어 useEffect가 실행된다.
import React, { useState, useEffect } from 'react';
import axios from 'axios';

function Users() {
    const [users, setUsers] = useState([]);
    const [error, setError] = useState(null);
    const [loading, setLoading] = useState(false);
    
    async function fetchUser() {
    // 함수가 처음 시작할 때 loading state를 true, 끝날 때 false로 설정
            setLoading(true);
            try {
                const response = await axios.get(
                    '<URL>'
                );
                setUsers(response.data);
            } catch (e) {
                setError(e);
            }
            setLoading(false);
        };
	
    useEffect(() => {
        fetchUser();
    }, []);
    
    const handleButtonClick = () => {
        fetchUser();
    }
    
    const userName = users.map(
        (user) => (<li key={user.id}> {user.name} </li>)
    );
    
    if(loading)
        return <h4>로딩중...</h4>;
    return (
        <>
            <h4>사용자 리스트</h4>
            <div> {userName} </div>
            {/* 버튼 클릭 시 fetchUser() 함수를 불러오는 이벤트를 등록 */}
            <button onClick={handleButtonClick}>다시 불러오기</button>
        </>
    );
}

useReducer로 API 다루기

  • reducer 함수를 정의해서 action.type에 따라 어떤 변화를 줄 것인지 명시한다.
function reducer(state, action) {
    switch (action.type) {
        case 'LOADING':
            return {
                loading: true,
                data: [],
                error: null
            };
        case 'SUCCESS':
            return {
                loading: false,
                data: action.data,
                error: null     
            };
        case 'FAIL':
            return {
                loading: false,
                data: [],
                error: action.error
            };
        default:
            throw new Error();
    }
}
  • 그리고 컴포넌트에서 reducer를 사용하기 위해 useReducer를 선언한다.
    const initState = { loading: false, data: [], error: null }
    const [state, dispatch] = useReducer(
        reducer, 
        initState,
    );
  • dispatch를 이용해 상태 변화를 관리한다.
async function fetchUser() {
        try {
            // dispatch를 이용해 state를 설정하는 코드입니다.
            dispatch({ type: 'LOADING' });
            const response = await axios.get(
                'https://jsonplaceholder.typicode.com/users'
            );
            dispatch({ type: 'SUCCESS', data: response.data });
        } catch (e) {
            dispatch({ type: 'FAIL', error: e });
        }
    };
    
    useEffect(() => {
        fetchUser();
    }, []);
    
    // useReducer의 state를 불러오는 코드
    const { loading, data, error } = state;

Pokemon API 호출하기

  • 여기를 참고해서 만들었다. 아래 주소에는 html을 이용해 만드는 방법이 소개되어 있다.
    👉 Using Axios to Pull Data from a REST API

  • React를 이용해 Pokemon API를 호출해서 포켓몬 리스트를 출력해 본다.

<App.js>

import React, { useState, useEffect } from 'react';
import axios from 'axios'
import PokemonList from './PokemonList'
import Pagination from './Pagination';

function App() {
  const [pokemon, setPokemon] = useState([])
  const [currentPageUrl, setCurrentPageUrl] = useState("https://pokeapi.co/api/v2/pokemon")
  const [nextPageUrl, setNextPageUrl] = useState()
  const [prevPageUrl, setPrevPageUrl] = useState()
  const [loading, setLoading] = useState(true)


  const [] = useState()
  

  useEffect(() => {
    setLoading(true)
    let cancel
    // 포켓몬 데이터를 불러오고 cancelToken을 함께 넘겨준다. 그리고 state를 설정한다.
    async function fetch() {
        const response = await axios.get(currentPageUrl, { cancelToken: new axios.CancelToken(c => cancel = c) })
        console.log(response.data);
        
        setLoading(false);
        setNextPageUrl(response.data.next);
        setPrevPageUrl(response.data.previous);
        setPokemon(response.data.results.map(p=> p.name))
    }
  
    fetch();
    
    
    // 페이지 이동이 취소되는 경우 현재 페이지를 유지
    return () => cancel()
  }, [currentPageUrl])

  // 다음 페이지로 이동
  function gotoNextPage() {
    setCurrentPageUrl(nextPageUrl)
  }

  // 이전 페이지로 이동
  function gotoPrevPage() {
    setCurrentPageUrl(prevPageUrl)
  }
  
  // 로딩 상태인 경우 로딩중을 출력
  if (loading) return "로딩중..."
  
  return (
    <>
      <PokemonList pokemon={pokemon} />
      <Pagination
        gotoNextPage={nextPageUrl ? gotoNextPage : null}
        gotoPrevPage={prevPageUrl ? gotoPrevPage : null}
      />
    </>
  );
}

export default App;

<PokemonList.js>

import React from 'react'

export default function PokemonList({ pokemon }) {
  return (
    <div>
      {pokemon.map(p => (
        <div key={p}>{p}</div>
      ))}
    </div>
  )
}

<Pagination.js>

import React from 'react'

export default function Pagination({ gotoNextPage, gotoPrevPage }) {
  return (
    <div>
      {gotoPrevPage && <button onClick={gotoPrevPage}>Previous</button>}
      {gotoNextPage && <button onClick={gotoNextPage}>Next</button>}
    </div>
  )
}

🐧 이 글은 엘리스 AI 트랙 2기 react 강의 내용을 바탕으로 작성되었습니다.

profile
쿄쿄

0개의 댓글