callback과 promise

With·2021년 4월 22일
0

오늘의 학습

1. Callback 과 Promise


callback function 이 필요한 이유

자바스크립트는 싱글스레드이기때문에 기본적으로 동기적으로 코드를 실행한다. 모든 코드를 동기적으로 실행할 때 발생할 수 있는 문제점은 앱 성능 저하를 발생시킬 수 있다는 것이다.

다만, 비효율성을 극복하기 위한 비동기처리 메서드이지만 '문제'가 발생한다.


const getName = (name) => {
  setTimeout(() => {
    const myName = name;
    return myName;
  }, 1000);
};

const showName = () => {
  console.log(getName());
};

getName('with');
showName();


// --- 결과 ---
// undefined

getName()은 파라미터를 받아 1초 뒤에 그 파라미터를 myName으로 반환한다. 그리고 showName()getName()에서 반환한 이름을 콘솔에 표시한다.

하지만 setTimeout이 비동기로 처리되는 메서드이기 때문에 showName()은 앞선 코드를 기다리지 않고 바로 실행된다. 1초 뒤에 name을 리턴하기 때문에 당장 showName()에서 보여질 이름값이 없는 것이다.

우리가 생각한 정상적인 작동은 showName()getName()이 이름을 리턴을 모두 마치기를 기다렸다가 실행되는 것이다. 이렇게 비동기로 처리 되는 것을 기다린 후에 실행되도록 명령하는 것이 callback 이다. 콜백에 showName()을 넣어주면 getName()이 끝난 후 그 값을 받아 콘솔에 이름을 보여줄 것이다.

callback function을 넣어보자

const getName = (name, callback) => {
  setTimeout(() => {
    const myName = name;
    callback(myName);
  }, 1000);
};

const showName = (name) => {
  console.log(name);
};

getName('with', (name) => {
  showName(name);
});

getName() 두번째 파라미터에 callback을 넣어주고, myName선언 후 callback 함수를 실행시킨다.

함수를 실행할 때는 함수 선언에서와 같이 두번째 파라미터 자리에 비동기 코드가 실행된 후에 실행할 함수를 넣어준다. 여기에서는 showName()일 것이다. showName() 파라미터에 들어가는 값은 getName() 선언 시, callback()에 들어간 파라미터를 가져오게 된다. 즉, myName을 받아오는 것이다.

이렇게 비동기로 실행되는 메서드에서 비롯되는 문제는 callback function을 통해 해결 할 수 있지만, 단점은 가독성이 떨어지고 복잡하다는 것이다.

이러한 것을 해결하기 위해 Promise가 나왔고, 또한 이것을 더 편하게 사용하고자 async, await이 나왔다.

fetchaxios에서는 Promise로 반환되는 경우가 많기때문에 정확한 사용을 위해서는 callback function의 원리, 그리고 Promise의 원리에 대해 구체적으로 알 필요가 있다.

2. REST API (feat. Axios, hooks)


useState를 이용한 상태관리


// Axios를 이용한 Get 기본구조

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const Users = () => {
  // state는 3개를 만든다. 
  // 정보를 담을 것, 로딩, 에러
  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchUsers = async () => {
    // async & await을 통해 비동기처리
    // try, catch 구문을 통해 '성공', 'error' 감지
    try {
      setUsers(null); // state initializing
      setError(null); // state initializing
      setLoading(true); // loading start!
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/users/'
      );
      setUsers(response.data);
    } catch (e) {
      setError(e); // error 발생
    }
    setLoading(false); // loading end!
  };

  useEffect(() => { //useEffect를 통해 컴포넌트가 생성될 때 api 연동 함수 실행
    fetchUsers(); 
  }, []);

  // 조건에 따라 렌더링 화면 분기
  if (loading) return <div>로딩중..</div>; 
  if (error) return <div>에러가 발생했습니다.</div>;
  if (!users) return null;

  return (
    <>
      <ul>
        {users.map((item) => (
          <li key={item.id}>{item.username}</li>
          //배열을 map할 땐, 항상 key 값을 부여한다.
        ))}
      </ul>
      <button onClick={fetchUsers}>다시 불러오기</button>
      <!-- api 다시 불러오는 버튼  --!>
    </>
  );
};

export default Users;

리액트에서 axios 를 이용하여 REST API를 호출하는 가장 기본적인 구조이다.

이것을 바탕으로 useReducer 를 사용하거나 또는 redux 를 이용하여 상태관리의 편리함을 추가한다.

useReducer 를 이용한 상태관리

import React, { useEffect, useReducer } from 'react';
import axios from 'axios';

// reducer
const reducer = (state, action) => {
  switch (action.type) {
    case 'LOADING':
      return {
        loading: true,
        data: null,
        error: null,
      };
    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,
        error: null,
      };
    case 'ERROR':
      return {
        loading: false,
        data: null,
        error: action.error,
      };
    default:
      throw new Error(`Unhandled action type : ${action.type}`);
  }
};

const Users = () => {
  //initailState
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: null,
  });


  const fetchUsers = async () => {
    // dispatch를 통해 reducer로 action을 전달하고 reducer는 전달받은 action을 참조하여 switch 문에 따라 새로운 state를 생성한다.
    dispatch({ type: 'LOADING' });
    try {
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/users'
      );
      dispatch({ type: 'SUCCESS', data: response.data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  const { loading, data: users, error } = state;
  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다.</div>;
  if (!users) return null;

  return (
    <>
      <ul>
        {users.map((item) => (
          <li key={item.id}>{item.username}</li>
        ))}
      </ul>
      <button onClick={fetchUsers}>다시 불러오기</button>
    </>
  );
};

export default Users;

useReducer를 이용하면, reducer를 export해서 다른 컴포넌트에서도 이용할 수 있다. 이로 인해 useState를 필요 이상 많이 사용하는 것을 방지 할 수 있다.


끝.

profile
주니어 프론트엔드 개발자 입니다.

0개의 댓글