React 3

j0yy00n0·2025년 5월 13일
post-thumbnail

2025.04.14

React

Async

intro

동기 작업(Synchronous) : 하나의 작업을 실행하고 마친 뒤에 다음 작업을 순차적으로 실행함(작업이 완료될 때까지 다음 코드 실행 블로킹)
비동기 작업(Asynchronous) : 메인 흐름은 멈추지 않는 상태에서 특정 작업들을 백그라운드에서 처리하여 동시에 처리하는 것처럼 실행함(넌블로킹)

  • 비동기 작업 할 때 가장 많이 사용하는 방식은 콜백 함수를 이용하는 방식이다
  • 콜백 지옥 : 비동기 작업이 연속적으로 중첩되며 콜백 함수 안에 또 콜백 함수가 들어가는 구조
  • 콜백 지옥 시 가독성과 유지보수가 좋지 않다.

Promise

비동기 작업을 더 직관적이고 체계적으로 처리하기 위해 ES6에서 도입된 기능

  • Promise 객체는 비동기 작업의 결과를 성공(resolve), 실패(reject)로 처리
  • .then(), .catch(), .finally() 메소드 체이닝을 통해 가독성이 좋아지고 콜백 지옥 문제를 해소
  • 최신 브라우저는 ES6를 지원하므로 Babel 없이도 사용 가능
<script>
        
    function increase(number) {

       const promise = new Promise((resolve, reject) => {
            setTimeout(
                () => {
                    const result = number + 10;

					/* 실패 시 reject*/
                    if(result > 50) {
                        const e = new Error('NumberTooBig');

                        return reject(e);
                    }
						
                    /* 성공 시 resolve */
                    resolve(result);
                },
                1000
            );
       });

       return promise;
    }

    console.log(increase(0));

    increase(0)
        .then(number => {                   // promise 내부의 resolve가 호출되고 결과가 담기고 난 뒤가 then이 실행되는 시점
            console.log(number);

            return increase(number);        // increase(10);
        })
        .then(number => {
            console.log(number);

            return increase(number);        // increase(20);
        })
        .then(number => {
            console.log(number);

            return increase(number);        // increase(30);
        })
        .then(number => {
            console.log(number);

            return increase(number);        // increase(40);
        })
        .then(number => {
            console.log(number);

            return increase(number);        // increase(50);
        })
        .then(number => {
            console.log(number);
            console.log('end');
        })
        .catch(e => {                       // reject에 들어간 에러 객체를 콜백함수의 인수로 넣어준다.
            console.log(e, '가 발생했네');
        })
        .finally(() => {
            console.log('finally 실행...');
        });
</script>
  • Promise를 리턴하는 함수는 사용할 때 Promise 객체가 된다
  • 함수 호출 후에 promise 객체 반환
  • Promise는 비동기 작업이 완료될 때 resolve 또는 reject로 결과를 전달
  • then() 메서드는 resolve된 값을 받아 다음 작업을 수행
  • catch()는 reject된 에러를 처리
  • finally()는 성공, 실패와 무관하게 항상 실행

Async - Await

promise를 더 쉽게 사용할 수 있게 도와주는 방법

  • await가 달린 함수의 결과인 promise에 담긴 결과(promise 내부의 resolve에 담긴 결과)를 반환
  • await가 달린 비동기 처리들은 동기식으로 동작
  • 비동기 처리의 흐름을 순차적으로 작성할 수 있게 해서 동기식처럼 보이게 한다.
<script>

    function increase(number) {
       const promise = new Promise((resolve, reject) => {
            setTimeout(
                () => {
                    const result = number + 10;

                    if(result > 50) {
                        const e = new Error('NumberTooBig');

                        return reject(e);
                    }

                    resolve(result);
                },
                1000
            );
       });

       return promise;
    }

    async function run() {
        try{
            let result = await increase(0);
            console.log(result);

            result = await increase(result);
            console.log(result);

            result = await increase(result);
            console.log(result);

            result = await increase(result);
            console.log(result);

            result = await increase(result);
            console.log(result);

            result = await increase(result);
            console.log(result);
        } catch(e) {
            console.log(e, '가 발생했네');
        }
    }

    run();
</script>

API 이용

Javascript를 사용하면 필요할 때 서버에 네트워크 요청을 보내고 새로운 정보를 받아올 수 있다.

  • Ajax를 이용해 페이지 새로고침 없이(비동기식으로) 서버에서 데이터를 가져올 수 있다
  • Ajax는 과거에 많이 쓰였으나, 현재는 fetch나 axios 같은 최신 API를 더 많이 사용

Fetch api

  • let promise = fetch(url, [options]);
  • url : 접근하고자 하는 url, 요청을 보낼 대상 주소 (API 엔드포인트)
  • options : 선택 매개변수로 http method(GET, POST, PUT, DELETE 등)나 headers, body 내용을 객체로 지정
  • options에 아무 값도 넣지 않으면 기본 GET 메소드로 요청
  • fetch()를 호출하면 브라우저는 네트워크 요청을 보내고 promise 객체를 반환
  • 반환받은 promise가 내장 클래스 Response 인스턴스와 함께 이행(resolve) 상태
  • 이 시점에 본문(body)이 도착하기 전이지만 개발자는 응답 헤더를 보고 요청이 성공적으로 처리되었는지 확인할 수 있다
  • 네트워크 문제로 존재하지 않는 경로 혹은 http 요청을 보낼 수 없는 상태에서는 promise는 거부 상태, .catch() 또는 try...catch로 처리 가능

fetch + async/await 기반 비동기 API 호출

<div id="root"></div>
<script type="text/babel">

async function callAPI() {
	/* fetch 호출 -> Promise 객체 반환 */
    const promise = fetch('https://jsonplaceholder.typicode.com/users');
    console.log(promise);  // [[PromiseState]]: "pending"

    /* PromiseResult라는 프로미스 안의 프로퍼티에는 직접 접근이 불가능
       [[]]으로 감싸져 있는 곳은 직접 접근이 불가능 
       따라서 반드시 await 또는 .then()으로 결과를 꺼내야 함 */
    console.log(promise['[[PromiseResult]]']);

    /* async await를 활용해서 실제 응답(Response 객체) 꺼내기 */
    const response = await promise;
    console.log(response);

	/* 응답 상태 코드 확인 */
    console.log(`응답 상태: ${response.status}`);

	/* 응답 헤더 출력 (Headers는 이터러블 객체) */
    console.log('응답 헤더');
    // console.log(response.headers);
        
    /* response.headers에 대한 내용 중 일부는 숨김 프로퍼티([Symbol.iterator])라서 for of문으로 확인함 */
    /* Symbol.iterator는 이 객체가 이터러블(반복 가능한 객체)임을 의미하는 표준 속성
       .next().value는 그 중 첫 번째 헤더 [key, value]를 반환 */
    // console.log(response.headers[Symbol.iterator]().next().value);
    /* response.headers는 이터러블이기 때문에 
       for...of문에서 구조 분해 할당으로 [key, value]로 순회 
       헤더 전체를 "Content-Type: application/json" 같은 형태로 출력*/
    for(let [key, value] of response.headers) {
            console.log(`${key}: ${value}`);
    }

	/* 본문(body) 사용 여부 체크 */
    console.log(`본문 내용 사용 여부: ${response.bodyUsed}`);

    /* Response 객체의 text()메소드 */
    // const responseText = await response.text();
    // console.log(responseText);

    /* Response 객체의 json()메소드: 결과로 넘어온 json 문자열을 파싱(문자열을 잘라서 js 객체로 변환)해서 promise 객체를 반환 
       직렬화 역직렬화
    */
    const json = await response.json();
    console.log('json' , json);

	/* 파싱된 객체 배열 반복 출력 */
    for(let i = 0; i < json.length; i++){
        console.log(json[i]);
    }

    console.log(`본문 내용 사용 여부: ${response.bodyUsed}`);

    /* 응답을 1회 받고 난 후 body 내용을 확인 후에는 더 이상 응답 body내용에 접근할 수 없다. */
    // json = await response.json();
    // console.log(json);

    // console.log('end');      // fetch가 비동기 방식으로 동작한다는 걸 확인하기 위한 출력 구문
}

function App() {
    const onClickHandler = () => {
        callAPI();
    }

    return <button onClick={onClickHandler}>API 요청</button>
}
   
ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>

fetch - then 사용

<div id="root"></div>

<script type="text/babel">
    function callAPI() {

        /* 1. API 요청 */ 
        fetch('https://jsonplaceholder.typicode.com/users')
        /*응답(Response 객체) 처리*/
        .then((response) => {
            console.log(response);
            /* 본문(body)을 JSON으로 파싱 (비동기) */
            return response.json();
        /* 파싱된 결과 사용 */
        }).then((json) => {
        	/* 배열 형태로 출력됨 */
            console.log(json);
        });
    }

    function App() {
        const onClickHandler = () => {
            callAPI();
        }

        return <button onClick={onClickHandler}>API 요청</button>
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>

Axios

JavaScript의 내장 함수가 아니기 때문에, 사용 전에 CDN이나 npm 설치가 필요

  • fetch와 달리 응답 본문을 따로 .json()으로 파싱하지 않아도 된다
  • 응답 객체(response)의 data 속성에 이미 파싱된 JSON 결과가 담겨 있다
  • axios.get('url');
  • axios.post(url, data, config);
/* javascript 내장 함수가 아니기 때문에 CDN 방식으로 링크 추가 */
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>



<div id="root"></div>

<script type="text/babel">

    function callAPI() {
        // console.log('실행되나');
        axios.get('https://jsonplaceholder.typicode.com/users')
             .then(res => {
                console.log(res);
                console.log(res.data)})
             .catch(err => console.log(err));
    }

    function App() {
        const onClickHandler = () => {
            callAPI();
        }
        return <button onClick={onClickHandler}>API 요청</button>
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>

Rendering Component After Fetch

비동기 작업은 실행 순서를 보장하지 않기 때문에, 작업의 순서가 중요할 경우 Promise나 async/await을 사용해 명시적으로 순서를 제어해야 한다

Rendering Component After Fetch - Promise 방식

  • Promise에서 .then()을 사용하면, 이전 작업이 완료된 후 다음 작업을 실행하게 되어 순서를 제어할 수 있다.
  • 여러 개의 비동기 작업이 있을 경우, 작업 결과를 먼저 값을 받아서 resolve로 넘기고, 그 결과를 .then() 또는 await로 받아 처리
  • 비동기 작업의 결과를 setState() 등으로 저장해 놓고, 이후 로직에서 해당 값을 사용
<div id="root"></div>

<script type="text/babel">

    const API_KEY = '123456789qwerasdfzxcv';

    const {useEffect, useState} = React;

    function Weather() {

        const [position, setPosition] = useState({});
        const [cityName, setCityName] = useState('');
        const [weather, setWeather] = useState({});
        const [wind, setWind] = useState({});

        useEffect(() => {
                
            new Promise((resolve, reject) => {
                navigator.geolocation.getCurrentPosition((currentPosition) => {
                        
                    setPosition({
                        longitude: currentPosition.coords.longitude,
                        latitude: currentPosition.coords.latitude
                    })

                    resolve(currentPosition.coords);
                });
            }).then(coords => {
                    fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${coords.latitude}&lon=${coords.longitude}&appid=${API_KEY}`)
                .then(response => response.json())
                .then(json => {
                    console.log(json);
                    console.log(json.name);             // 조회 된 도시 이름 문자열
                    console.log(json.weather[0]);       // 조회 된 날씨 객체    
                    console.log(json.wind);             // 조회 된 바람 객체

                    /* state 3개에 각각 따로 저장 */
                    setCityName(json.name);
                    setWeather(json.weather[0]);
                    setWind(json.wind);
                });
            });
        },
        []
        );

        return (
            <>
                <h3>현재 위치</h3>
                <h4>{`경도: ${position.longitude} 위도: ${position.latitude}`}</h4>
                <h4>{`조회 도시: ${cityName}`}</h4>
                <h4>{`날씨: ${weather.main} 날씨설명: ${weather.description}`}</h4>
                <h4>{`풍향: ${wind.deg}도 풍속: ${wind.speed}m/s`}</h4>
            </>
        );
    }

    function App() {
        return (
            <>
                <h1>오늘의 날씨</h1>
                <Weather/>
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>

Rendering Component After Fetch - Async/Await 방식

  • async/await을 사용하면 비동기 함수 호출 결과를 변수에 할당하고, 그 값을 기반으로 다음 작업을 수행하는 등 동기적인 코드 흐름처럼 구성
<div id="root"></div>

<script type="text/babel">

    const API_KEY = '123456789qwerasdfzxcv';

    const {useEffect, useState} = React;

    function Weather() {

        const [position, setPosition] = useState({});
        const [cityName, setCityName] = useState('');
        const [weather, setWeather] = useState({});
        const [wind, setWind] = useState({});

        function getPosition() {
            return (
                new Promise((resolve, reject) => {
                    navigator.geolocation.getCurrentPosition((currentPosition) => {
                        setPosition({
                            longitude: currentPosition.coords.longitude,
                            latitude: currentPosition.coords.latitude
                        })

                        resolve(currentPosition.coords);
                    });
                })
            );
        }

        function getWeather(coords) {
            return fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${coords.latitude}&lon=${coords.longitude}&appid=${API_KEY}`)
                   .then(response => response.json());
        }

        useEffect(() => {
            async function setWeatherState() {
                const currentPosition = await getPosition();
                const result = await getWeather(currentPosition);
                setCityName(result.name);
                setWeather(result.weather[0]);
                setWind(result.wind);
            }
            setWeatherState();
        },
        []
        );

        return (
            <>
                <h3>현재 위치</h3>
                <h4>{`경도: ${position.longitude} 위도: ${position.latitude}`}</h4>
                <h4>{`조회 도시: ${cityName}`}</h4>
                <h4>{`날씨: ${weather.main} 날씨설명: ${weather.description}`}</h4>
                <h4>{`풍향: ${wind.deg}도 풍속: ${wind.speed}m/s`}</h4>
            </>
        );
    }

    function App() {
        return (
            <>
                <h1>오늘의 날씨</h1>
                <Weather/>
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>

참고

직렬화(Serialization)

  • 객체를 문자열로 바꾸는 과정
  • 자바 객체 → JSON 문자열
  • 주로 데이터를 전송, 저장하기 위해 사용

역직렬화(Deserialization)

  • 문자열을 다시 객체로 변환하는 과정
  • JSON 문자열 → 자바스크립트 객체
  • 프론트엔드에서 API 응답을 받아 처리할 때 사용

외부 API 호출하는 경우

  • 일부 API는 API Key를 요구하며, 이 키를 통해 요청자의 권한과 사용량을 제한한다.
  • API DOC 공식 문서를 참고하여 요청방식, 필요한 파라미터, 헤어 설정, 응답 형식, 인증 방식(API key) 등을 확인하여 필요한 값을 사용한다.
  • https://github.com/dl0312/open-apis-korea
profile
잔디 속 새싹 하나

0개의 댓글