axios 문법

axios는 fetch보다 문법이 조금 더 간단하고 다양한 편의기능을 제공하기 때문에 실무에서 많이 사용한다.

axios는 서드파티 모듈이기 때문에 설치를 해줘야 하는데 터미널을 키고 npm install axios 라고 입력해주면 된다.

axios 설치

axios는 HTTP 메소드 이름과 동일한 메소드를 사용하고 리스폰스 바디를 data 프로퍼티로 접근할 수 있다.

GET 리퀘스트

axios로 GET 리퀘스트를 보내려면 axios.get 메소드를 사용하면 된다.

// axios
import axios from 'axios'; //axios import하기

async function getColorSurvey(id) {
  const res = await axios.get(`https://learn.codeit.kr/api/color-surveys/${id}`);
  return res.data;
}

axios 역시 promise를 리턴하기 때문에 앞에 await을 적어줘야 한다. 그리고 axios에서는 바디 내용을 파싱할 필요 없이 그냥 data 프로퍼티로 가져오면 된다. 상태 코드 status나 헤더 headers를 가져올 수도 있다. 그리고 그걸 데이터 변수에 할당할 필요 없이 그냥 리턴해줄 수 있다.

// fetch

async function getColorSurvey(id) {
  const res = await fetch(`https://learn.codeit.kr/api/color-surveys/${id}`);
  const data = await res.json();
  return data;
}

쿼리 파라미터도 조금더 쉽게 처리할 수 있는데 쿼리파라미터를 보낼 경우 url 객체를 사용할 필요없이 params 옵션을 사용하면 된다.

// axios

export async function getColorSurveys(params = {}) {
  const res = await axios.get(
    'https://learn.codeit.kr/api/color-surveys', 
    { params }, //이 문법은 params라는 프로퍼티로 params 객체를 설정하는 것이다.
  );
  return res.data;
}

이렇게 쿼리 파라미터를 담고 있는 객체를 전달하면 객체에 있는 프로퍼티들로 알아서 쿼리 스트링을 만들고 URL 뒤에 붙여서 리퀘스트를 보내준다. 만약 프로퍼티 값이 null이나 undefined이면 그 프로퍼티를 무시하고 쿼리 스트링을 만들어준다.

// fetch

async function getColorSurveys(params = {}) {
  const url = new URL('https://learn.codeit.kr/api/color-surveys');
  Object.keys(params).forEach((key) =>
    url.searchParams.append(key, params[key])
  );
  const res = await fetch(url);
  const data = await res.json();
  return data;
}

params 외에 헤더 같은 정보도 옵션으로 설정할 수 있다. 

POST 리퀘스트

POST는 axios.post를 사용하면되고, 각 HTTP 메소드는 동일한 이름의 axios 메소드를 쓰면 된다. 그리고 POST 메소드는 바디로 전달할 데이터를 두번째 아규먼트로 받는데 자바스크립트 객체를 그대로 사용할 수 있다.

// axios

async function createColorSurvey(surveyData) {
  const res = await axios.post('https://learn.codeit.kr/api/color-surveys', surveyData);
  return res.data;
}

axios가 알아서 자바스크립트 객체를 JSON 문자열로 변환해 주기 때문에 JSON.stringify 같은 메소드를 쓰지 않아도 된다. 그리고 헤더도 바디 데이터를 보고 알아서 설정해 주기 때문에 내가 설정하지 않아도 된다.

// fetch

async function createColorSurvey(surveyData) {
  const res = await fetch('https://learn.codeit.kr/api/color-surveys', {
    method: 'POST',
    body: JSON.stringify(surveyData),
    headers: {
      'Content-Type': 'application/json',
    },
  });

  if (!res.ok) {
    throw new Error('데이터를 생성하는데 실패했습니다.');
  }

  const data = await res.json();
  return data;
}
  • 리퀘스트에 바디가 필요 없는 get이나 delete는 옵션을 두 번째 파라미터로 받는다.
  • 바디가 필요한 post, patch, put은 바디 데이터를 두 번째 아규먼트로 받고,
  • 옵션을 세 번째 아규먼트로 받는다. 옵션에서는 헤더, 쿼리 파라미터, 타임아웃 등 다양한 설정을 할 수 있다. <참고>

리퀘스트에 바디가 필요 없는 get이나 delete는 옵션을 두 번째 파라미터로 받는다
바디가 필요한 post, patch, put은 바디 데이터를 두 번째 아규먼트로 받고

axios 인스턴스

리퀘스트마다 공통되는 부분이 있으면 인스턴스를 생성하고 인스턴스로 리퀘스트를 보내면 된다. 예를들어 리퀘스트 URL은 보통 경로만 다르고 앞부분은 항상 동일하다. base URL로 앞부분을 설정하고 리퀘스트 timeout을 3초로 설정해보자.

const instance = axios.create({
  baseURL: 'https://learn.codeit.kr/api', //baseURL에서 URL은 모두 대문자로 작성하자
  timeout: 3000,
}); //이제 인스턴스에서 baseURL을 설정했으니  axios를 사용하는 부분들을 바꿔주면 된다.

async function getColorSurveys(params = {}) {
  const res = await instance.get(`/color-surveys`, { //나머지 함수들도
    params, //비슷하게 바꿔주면 된다.
  });
  return res.data;
}

인스턴스로 리퀘스트를 보내고 있기 때문에 모두 baseURL과 timeout이 설정되어 있다. 이제 URL 앞부분이 바뀌거나 해도 한군데서만 바꾸면 된다. 인스턴스를 만들 때 설정할 수 있는 옵션은 리퀘스트를 보낼 때 설정할 수 있는 옵션과 똑같다.

axios 오류 처리

fetch 함수는 리퀘스트 자체가 실패했을 때만 promise가 reject되고 400이나 500 에러 리스폰스가 돌아왔을 때는 promise가 reject 되지 않았다.

axios는 리퀘스트 자체가 실패하거나 리스폰스의 상태 코드가 실패(4XX, 5XX)를 나타내면 Promise를 reject 한다. 그러니까 리퀘스트가 성공하고 200대의 상태코드를 가진 리스폰스가 돌아와야만 promise가 fulfilled가 되는 것이다. (그래서 오류처리가 좀 더 편하다.) axios 함수들을 호출할 때 try catch로 감싸주면 된다.

//main.js
import { getColorSurveys, getColorSurvey, createColorSurvey } from './axiosApi.js';

try {
  const survey = await getColorSurvey(123);
  console.log(survey);
} catch (e) {
  console.log('오류가 발생했습니다.');
  console.log(e.message);
}

에러 객체의 메세지는 axios가 알아서 설정해 주지만 이거 대신 에러 리스폰스 바디에 있는 메시지를 사용할 수도 있다. 에러 리스폰스가 돌아오면 axios는 에러 객체의 리스폰스를 저장해준다. 

axios가 알아서 정해준 에러 객체 메세지

//main.js
import { getColorSurveys, getColorSurvey, createColorSurvey } from './axiosApi.js';

try {
  const survey = await getColorSurvey(123);
  console.log(survey);
} catch (e) {
  console.log('오류가 발생했습니다.');
  console.log(e.response); //response 프로퍼티로 변경
}

이렇게 response 프로퍼티로 접근할 수 있는데 이 response도 상태코드 status, 헤더 headers, 바디 데이터 data 프로퍼티가 있다.

//main.js
try {
  const survey = await getColorSurvey(123);
  console.log(survey);
} catch (e) {
  console.log('오류가 발생했습니다.');
  console.log(e.response.status); //상태코드
  console.log(e.response.data); //바디내용
}

프로퍼티를 더 붙여주면 에러 리스폰스의 상태코드와 바디내용 등 출력하고자 하는 내용만 따로 출력할 수도 있다.

상태코드 404, 바디내용 NF

//api.js
export async function createColorSurvey(surveyData) {
  const res = await axios.post('https://learn.codeit.kr/api/color-surveys', surveyData);
  return res.data;
}
//main.js
import { createColorSurvey } from './api.js';

const surveyData = {
  mbti: 'EEEE',
  colorCode: '#CDCDCD',
  password: '0000',
};

try {
  const newColorSurvey = await createColorSurvey(surveyData);
  console.log(newColorSurvey);
} catch (e) {
  if (e.response) { 
    // 리퀘스트는 성공했지만 상태 코드가 실패(4XX, 5XX)를 나타냄
    console.log(e.response.status);
    console.log(e.response.data);
  } else { 
    // 리퀘스트 자체가 실패
    console.log('리퀘스트가 실패했습니다.');
  }
}
Invalid MBTI //잘못된 MBTI

한 가지 주의해야할 점은 리스폰스가 돌아왔을 때만 리스폰스 객체가 존재한다는 것이다. status나 data같은 프로퍼티를 접근할 거라면 리스폰스 객체의 존재여부를 먼저 확인해야한다. 

네트워크 리퀘스트를 보내는 코드는 보통 웹 브라우저에서 실행된다. 웹 브라우저에서는 개발자 도구로 네트워크 액티비티를 확인 할 수 있다. (이번 포스팅의 목적 : 개발자 도구의 네트워크 기능 알아보기)

웹 개발을 할 때는 네트워크 리퀘스트를 보내는 부분에서 생각지 못한 버그가 발생할 수 있는데 이럴 때 개발자 도구가 굉장히 유용하게 쓰인다. 

<!-- index.html -->
<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>네트워크 요청 데모</title>
        <style>
            body {
                font-family: Arial, Helvetica, sans-serif;
                margin: 16px;
            }
            div {
                margin: 8px 0;
            }
            span {
                margin-right: 8px;
            }
        </style>
    </head>
    <body>
        <h2>네트워크 요청 데모</h2>
        <input id="mbti" placeholder="MBTI" />
        <input id="colorCode" placeholder="#000000" />
        <button>리퀘스트 보내기</button>
        <div class="status"></div>
        <div class="data"></div>
        <script type="module" src="demo.js"></script>
    </body>
</html>
//demo.js
import { getColorSurvey, createColorSurvey } from './api.js';

const btn = document.querySelector('button');
const statusDiv = document.querySelector('div.status');
const dataDiv = document.querySelector('div.data');
const mbtiInput = document.querySelector('#mbti');
const colorCodeInput = document.querySelector('#colorCode');

btn.addEventListener('click', async function (e) {
    statusDiv.textContent = '로딩 중...'; //이 함수는 우선 status를 로딩중으로 설정해주고
    dataDiv.innerHTML = '';
    try {
        const survey = await getColorSurvey(3); //이렇게 리퀘스트를 보낸다. getColorSurvey함수를 실행하고 있는데
        statusDiv.textContent = '완료'; //리스폰스가 돌아오면 status를 완료로 바꾸고
        dataDiv.innerHTML = `<span>${survey.mbti}</span><span>${survey.colorCode}</span>`; //dataDiv 안에 설문 객체의 mbti 필드와 colorCode 필드를 보여준다.
    } catch (e) { //만약 리퀘스트를 보내는 부분에서 에러가 나면
        statusDiv.textContent = '오류'; //status를 오류로 바꾸고 
        dataDiv.innerHTML = `<span>${e.message}</span>`; //에러 메세지를 dataDiv 안에서 보여준다.
    }
});

위 코드 그대로 실행해본 화면

페이지에는 그냥 폼 요소랑 버튼, 그리고 텍스트가 있다. html에서 body태그 안에 class status는 네트워크 요청 상태를 보여줄 div이고 class data div는 네트워크 요청 결과를 보여줄 div이다. 그리고 html 파일은 demo.js 파일과 연결되어 있는데 demo.js 파일을 보면 일단 API 함수들을 임포트하고 페이지에 있는 각종 요소들을 가져오고 있다. 그리고 중요한 이벤트 영역이 있는데 버튼을 클릭하면 안에 있는 함수(async function (e)부터 dataDiv.innerHTML = `<span>${e.message}</sapn>`;}});까지)가 실행된다.

이벤트의 상세 내용에 대해선 위에 있는 demo.js의 코멘트를 확인해보자.

개발자 도구의 Network 탭을 들어가보면 보이는 화면

개발자 도구가 열려 있는 상태라면 네트워크 요청이 기록되는데 새로고침을 해보면 어떤 기록들이 생성된다.

스티커가 위치한 부분이 기록들이다.

이 기록들 하나하나가 다 리퀘스트이다. 각 리퀘스트의 상태 코드(status), 타입(type), 어디서 리퀘스트를 보냈는지(Initiator), 사이즈(size), 걸린 시간(time) 등을 볼 수 있고, 마지막 waterfall은 리소스를 가져오는 과정과 타이밍을 그림으로 그린 것이다. 200ms 400ms 600ms 등 써 있는 중간 부분은 전체적인 waterfall을 보여주는 것이다.

type이 fetch라고 쓰여있는 리퀘스트를 선택해보면 waterfall이 조금씩 바뀌는것을 확인할 수 있다. 

그리고 컬럼 헤더에 마우스를 올려 우클릭을 해보면 다른 정보도 볼 수 있다. 리퀘스트 Method도 볼 수 있게 Method를 선택해보자.

메소드를 선택하면 메소드 칸이 추가된다.

이제 리퀘스트를 선택해보면 여러 탭을 클릭할 수 있다. 

Headers에서는 리퀘스트와 리스폰스의 헤더를 확인 할 수 있다.

그리고 Response는 리스폰스의 바디를 확인할 수 있는데, 

아래쪽에 라이언 스티커 옆에 있는 중괄호 아이콘

중괄호 아이콘을 클릭하면 압축된 결과와 예쁘게 출력된 결과를 확인할 수 있다.

그리고 Preview 탭은 리스폰스 내용의 미리보기를 보여준다. 지금처럼 JSON의 경우 큰 차이가 없지만 리스폰스 내용이 HTML인 경우 HTML에 대한 화면을 보여준다.

HTML에 대한 화면을 보여준다.

디버깅(debugging 컴퓨터 프로그램 개발 단계에서 발생하는 오류나 비정상적인 연산(버그)을 찾아내고 수정하는 작업 과정)을 하다보면 헤더가 제대로 설정되고 있는지, 예상된 리스폰스가 돌아오고 있는지 등을 확인해야 하는 경우가 많은데 그럴 때 Network 탭으로 리퀘스트, 리스폰스의 모든 정보를 볼 수 있기 때문에 아주 유용하다.

import { getColorSurvey, createColorSurvey } from './api.js';

const btn = document.querySelector('button');
const statusDiv = document.querySelector('div.status');
const dataDiv = document.querySelector('div.data');
const mbtiInput = document.querySelector('#mbti');
const colorCodeInput = document.querySelector('#colorCode');

btn.addEventListener('click', async function (e) {
    statusDiv.textContent = '로딩 중...';
    dataDiv.innerHTML = '';
    try { //POST 리퀘스트 보내기
        const surveyData = {
            mbti: mbtiInput.value, //Input 필드의 값을 받아서 surveyData 객체를 만들고
            colorCode: colorCodeInput.value, 
            password: '0000',
        };
        const survey = await createColorSurvey(surveyData); //createColorSurvey 함수의 아규먼트로 surveyData를 전달 
        statusDiv.textContent = '완료';
        dataDiv.innerHTML = `<span>${survey.mbti}</span><span>${survey.colorCode}</span>`;
    } catch (e) {
        statusDiv.textContent = '오류';
        dataDiv.innerHTML = `<span>${e.message}</span>`;
    }
});

이렇게 코드를 작성해보면, 개발자 창으로 가봤을 때 새로고침이 되면서 과거 기록이 삭제되어 있을것이다. 만약 기록을 지우지 않고 남기고 싶다면 preserve log를 선택하면 된다.

폼에 잘못된 mbti 정보를 입력하고 리퀘스트를 보내보자. 

preflight 라는 리퀘스트는 브라우저가 자동으로 보내는 건데 지금은 무시해도된다. 오류가 발생한 POST 리퀘스트를 클릭해보면 400 상태 코드가 돌아온걸 확인할 수 있고, payload 탭에서는 리퀘스트 바디를 확인할 수 있다. 리스폰스 바디는 그냥 문자열이다.

그럼 제대로 된 mbti를 입력하고 다시 리퀘스트를 보내보면 어떨까?

이번엔 리퀘스트가 성공적으로 처리됐다.
성공한 리퀘스트의 헤더, 페이로드, 리스폰스

여기에 자주 사용하는 기능 두가지가 더 있는데 

Filter 인풋창 옆으로 쭉 있는 항목들이 필터이다.

위에 있는 Filter를 이용해서 원하는 리퀘스트 타입만 볼 수가 있다. 예를 들어 fetch를 클릭하면 

이렇게 fetch 리퀘스트만 모아서 볼 수 있다. 원하는 리퀘스트를 찾을 때 아주 유용하다. 그리고 그 위에 있는 throttling 기능도 많이 사용하는데

이걸 클릭하면 3G처럼 느린 인터넷 속도, 아니면 아예 오프라인 상태를 흉내낼 수도 있다. 지금은 리퀘스트가 하도 빨리 처리되서 중간에 로딩되는 상태를 보기가 어려운데 여기서 slow 3G를 선택하면 

로딩 중...

중간에 로딩이 되는 상태를 확실하게 확인해 볼 수 있다.

<네트워크 기능 참조>

네트워크 요청을 할 땐 다양한 오류가 발생할 수 있다. 

// api.js
export async function getColorSurveys(params = {}) {
  const url = new URL('https://learn.codeit.krrrrr/api/color-surveys'); //일부러 오류만들기
  object.keys(params).forEach((key) =>
    url.searchParams.append(key, params[key])
  );
  
  const res = await fetch(url);
  const data = await res.json();

  return data;
}

export async function getColorSurvey(id) {
  const res = await fetch(`https://learn.codeit.kr/api/color-surveys/${id}`);
  const data = await res.json();
  return data;
}

export async function createColorSurvey(sueveyData) {
  const res = await fetch('https://learn.codeit.kr/api/color-surveys', {
    method: 'POST',
    body: JSON.stringify(surveyData),
    headers: {
      'Content-Type': 'application/json'
    }
  });

  const data = await res.json();
  return data;
}

일부러 오류를 발생하게 오타를 만든뒤 main.js 파일을 실행해보면 fetch가 실패했다고 나온다. 

fetch failed
결과 화면

주소를 찾을 수 없기 때문에 오류가 발생하고 fetch가 리턴하는 promise가 rejected 상태가 된다. 이 오류는 이제 너무나도 익숙한 try catch문을 이용해 처리할 수 있다.

드래그 한 부분

왼쪽의 드래그 한 부분을 try catch로 감싸거나 함수를 실제로 호출하는 부분을 try catch로 감싸줘도 된다. try문 안에서 throw되는 오류는 모두 잡아주기 때문에 상관이 없다. 

//main.js
import { getColorSurveys, getColorSurvey, createColorSurvey } from './api.js';

try {
  const data = await getColorSurvey(); 
  console.log(data);
} catch (e) {
  console.log('오류가 발생했습니다:');
  console.log(e.message);
}

함수를 호출하는 부분을 try catch문으로 감싸주고 코드를 다시 실행해보면 

이번엔 그냥 fetch failed라고 나온다. try문이 안에서 발생한 오류르 잡아서 catch문 안에 있는 코드가 실행된 것이다. 지금은 콘솔에 오류 메세지를 출력하지만 실제 웹사이트 코드라면 오류 메세지를 화면에 보여줄 수 있을 것이다. 

유효하지 않은 주소를 입력하는 것 외에도 유효하지 않은 헤더 이름을 사용하거나 헤더 값이 이상하면 리퀘스트 자체가 실패해서 fetch가 리턴하는 promise는 rejected 상태가 된다. 주의해야할 점은 리퀘스트 자체가 실패했을 때만 promise가 rejected 상태가 되고 400이나 500같은 에러 리스폰스가 돌아오는 경우에는 promise는 fulfilled 상태가 된다.

그러니까 예를 들어 잘못된 바디 내용을 보내서 400리스폰스가 돌아오거나 서버 측에서 오류가 발생해서 500 리스폰스가 돌아와도 fetch의  promise는 fulfilled 상태가 된다. 그래서 이걸 깔끔하게 처리하는 방법은 리스폰스의 상태 코드가 성공을 나타내지않으면 오류를 발생시키는 것이다. 

// api.js
export async function getColorSurveys(params = {}) {
  const url = new URL('https://learn.codeit.krrrrr/api/color-surveys'); //일부러 오류만들기
  object.keys(params).forEach((key) =>
    url.searchParams.append(key, params[key])
  );
  
  const res = await fetch(url);
  //오류 발생
  if (!res.ok) {
    throw new Error('데이터를 불러오는데 실패했습니다.');
  }
  
  const data = await res.json();
  return data;
}

export async function getColorSurvey(id) {
  const res = await fetch(`https://learn.codeit.kr/api/color-surveys/${id}`);
  const data = await res.json();
  return data;
}

export async function createColorSurvey(sueveyData) {
  const res = await fetch('https://learn.codeit.kr/api/color-surveys', {
    method: 'POST',
    body: JSON.stringify(surveyData),
    headers: {
      'Content-Type': 'application/json'
    }
  });

  const data = await res.json();
  return data;
}

여기서 추가한 res.ok는 리스폰스 상태 코드가 2로 시작하면 true, 그렇지 않으면 false를 리턴한다. if문 안에서 리스폰스의 실제 상태 코드나 바디내용을 보고 더 세밀한 처리를 해줄 수도 있다. 리퀘스트 자체가 실패해도 오류를 throw하고 오류를 나타내는 상태 코드가 돌아와도 오류를 throw 하는 것이다. 

이렇게 처리를 하는게 헷갈리지 않고 함수를 사용할 때 더 편리하다.

일부러 만들었던 주소 오류는 수정을 하고, 유효하지 않은 mbti를 쿼리 파라미터로 보내본다면, 

//main.js
import { getColorSurveys, getColorSurvey, createColorSurvey } from './api.js';

try {
  const data = await getColorSurvey({ mbti: 'EEEE' }); 
  console.log(data);
} catch (e) {
  console.log('오류가 발생했습니다:');
  console.log(e.message);
}

방금 작성한 메세지가 잘 출력되는걸 확인 할 수 있다. 만약 if (!res.ok) {throw new Error('데이터를 불러오는데 실패했습니다.');} 이 코드가 없었다면 잘못된 mbti를 전달했기 때문에 상태코드 400이 돌아올것이다. 상태코드 400이 돌아와도 아무 문제없이 다음줄이 실행되는데 이 때 상태코드 400에 해당하는 리스폰스의 형식도 JSON이라면 바디가 파싱되서 그대로 리턴된다. 함수 안에서 아예 오류가 발생하지 않는 것이다. 반면 리스폰스 바디 형식이 JSON이 아니라면 오히려 바디를 파싱하는 과정에서 오류가 발생한다. 

이렇게 다양한 케이스가 있어서 헷갈릴 수 있기 때문에 리스폰스가 성공적으로 처리되지 않으면 그냥 오류를 throw 하는 것이다. 나머지 코드도 비슷하게 바꿔주면 이렇게 할 수 있다.

// api.js
export async function getColorSurveys(params = {}) {
  const url = new URL('https://learn.codeit.kr/api/color-surveys');
  object.keys(params).forEach((key) =>
    url.searchParams.append(key, params[key])
  );
  
  const res = await fetch(url);
  //오류 발생
  if (!res.ok) {
    throw new Error('데이터를 불러오는데 실패했습니다.');
  }
  
  const data = await res.json();
  return data;
}

export async function getColorSurvey(id) {
  const res = await fetch(`https://learn.codeit.kr/api/color-surveys/${id}`);
  
  if (!res.ok) {
    throw new Error('데이터를 불러오는데 실패했습니다.');
  }
  
  const data = await res.json();
  return data;
}

export async function createColorSurvey(sueveyData) {
  const res = await fetch('https://learn.codeit.kr/api/color-surveys', {
    method: 'POST',
    body: JSON.stringify(surveyData),
    headers: {
      'Content-Type': 'application/json'
    }
  });
  
  if (!res.ok) {
    throw new Error('데이터를 생성하는데 실패했습니다.');
  }

  const data = await res.json();
  return data;
}

마무리 복습하기

fetch 오류는 크게 두가지가 있다. 

  1. URL이 이상하거나 헤더 정보가 이상해서 리퀘스트 자체가 실패하는 경우
  2. 리퀘스트는 성공적인데 상태코드가 실패를 나타내는 경우

여기서 조심해야 할 부분은 첫번째 경우에만 fetch의 promise가 rejected 상태가 된다는 것이다. 그래서 오류를 깔끔하게 처리하는 방법 중 하나는 두번째 경우에도 수동적으로 오류를 throw 해주는 것이다. 리퀘스트가 성공적으로 처리되고, 성공적인 리스폰스가 돌아왔을 때만 데이터를 리턴하고 나머지 모든 경우엔 오류를 발생시키는 것이다. 

그래야 API 함수를 사용하는 입장에서 try catch로 쉽게 로직을 구분할 수 있다.

fetch에 대한 오류를 처리할 때는 언제 promise가 rejected 되는지, 어떤 내용이 리스폰스 바디로 돌아오는지를 잘 생각해보도록 하자.

보통 웹 개발을 할 때는 aPI를 호출하는 함수들을 따로 모아두고 필요할 때 import해서 사용한다.

//api.js
//GET/api/color-surveys를 하는 함수부터 만들어보자.
export async function getColorSurveys() {
  const url = new URL('https://learn.codeit.kr/api/color-surveys');
  url.searchParams.append('offset', 10);
  url.searchParams.append('limit', 10);
  
  const res = await fetch(url);
  const data = await res.json();

  return data;
}

함수 안에서 await을 사용하니 async 키워드도 추가해주고, 데이터를 리턴하는것으로 바꿔주었다. 그리고 위 코드에선 쿼리 파라미터가 고정되어 있는데 쿼리파라미터를 함수 파라미터로 대신 받도록 바꿔준다.

//api.js
export async function getColorSurveys(params = {}) {
  const url = new URL('https://learn.codeit.kr/api/color-surveys');
  object.keys(params).forEach((key) =>
    url.searchParams.append(key, params[key])
  );
  
  const res = await fetch(url);
  const data = await res.json();

  return data;
}

params 객체의 프로퍼티를 돌면서 url.searchParams에 추가해 주는 코드이다. 예를 들어 params에 이런 객체( params = { offset: 5, limit: 10}; )가 전달된다면 url.searchParams에 offset은 5, limit은 10이 추가된다.

이제 이 함수를 import해서 사용하면 되는데 main.js에서 나머지 코드 부분을 코멘트 처리하고 코드를 작성해준다.

//main.js
import { getColorSurveys } from './api.js';

const data = await getColorSurveys(); //async 함수는 promise를 리턴하기 때문에 앞에 await을 적어준다.
console.log(data);

이 상태로 실행해봐도 이전과 같이 리스폰스가 잘 출력된다.  여기에서 쿼리 파라미터도 바로 사용할 수 있는데

//main.js
import { getColorSurveys } from './api.js';

const data = await getColorSurveys({ offset:20, limit:20 });
console.log(data);

결과값

이번엔 offset과 limit에 따라 next와 previous의 값이 돌아오고, 결과도 총 20개가 돌아온다.

// api.js
// GET/api/color-surveys를 하는 함수
export async function getColorSurveys(params = {}) {
  const url = new URL('https://learn.codeit.kr/api/color-surveys');
  object.keys(params).forEach((key) =>
    url.searchParams.append(key, params[key])
  );
  
  const res = await fetch(url);
  const data = await res.json();

  return data;
}

// GET/api/color-surveys/:id
export async function getColorSurvey(id) {
  const res = await fetch(`https://learn.codeit.kr/api/color-surveys/${id}`);
  const data = await res.json();
  return data;
}

// POST/api/color-surveys
export async function createColorSurvey(sueveyData) {
  const res = await fetch('https://learn.codeit.kr/api/color-surveys', {
    method: 'POST',
    body: JSON.stringify(surveyData),
    headers: {
      'Content-Type': 'application/json'
    }
  });

  const data = await res.json();
  return data;
}

main.js에 적혀있던 코멘트한 리퀘스트 함수들을 api.js 파일로 긁어와서 구분, 작성해준 뒤 실행을 시켜보도록 하자.

//main.js
import { getColorSurveys, getColorSurvey, createColorSurvey } from './api.js'; //함수들을 import하고

const data = await getColorSurvey(10); //getColorSurvey에 id 10을 전달
console.log(data);

테스트용으로 getColorSurvey에 id 10을 전달해보았다. 

id: 10

id 10에 해당하는 설문 객체가 잘 돌아온다. 

//main.js
import { getColorSurveys, getColorSurvey, createColorSurvey } from './api.js';

// 새로운 설문 객체를 만들어보자.
const surveyData = {
  mbti: 'ENFJ',
  colorCode: '#ABCD00',
  password: '0000'
};

const data = await createColorSurvey(surveyData); 
console.log(data);

설문 객체 생성 완료!

설문 객체도 잘 생성됐다. 

fetch 함수는 기본적으로 GET 리퀘스트를 보냈는데 POST나 다른 종류의 리퀘스트는 어떻게 보낼 수 있을까?

fetch 함수의 두번째 아규먼트로 다양한 옵션을 넘겨줄 수 있다. POST 리퀘스트를 보내려면 옵션의 method 프로퍼티를 POST로 설정하면 된다.

//main.js
fetch('https://learn.codeit.kr/api/color-surveys', {
  method: 'POST' //POST가 아니라도 PATCH나 DELETE로 설정도 가능하다.
});
더보기
  • GET : 리소스 조회 (최근에는 Representation이라는 이름을 많이 사용)
  • POST : 요청 데이터 처리, 주로 등록에 사용된다.
  • PUT : 리소스를 대체, 해당 리소스가 없으면 생성한다.
  • PATCH : 리소스 부분 변경
  • DELETE : 리소스 삭제

POST 리퀘스트는 보통 바디를 같이 전달하는데, color-survey API의 경우 MBTI, 좋아하는 색깔의 헥스코드, 그리고 설문에 대한 비밀번호를 바디로 전달해야한다.

//main.js
const surveyData = {
  mbti: 'ENFP',
  colorCode: '#ABCDEF',
  password: '0000' //여기서 비밀번호는 설문객체를 생성한 사람만이 나중에 수정/삭제를 할 수 있게 하는 용도
};

fetch('https://learn.codeit.kr/api/color-surveys', {
  method: 'POST',
  body: surveyData //리퀘스트 바디도 옵션으로 설정 가능, body라는 프로퍼티로 설정
});

중요한 점! surveyData는 자바스크립트 객체이고, 외부로 데이터를 주고 받을 때는 JSON 문자열을 사용한다. 그래서 surveyData를 JSON 문자열로 변환해주어야 한다. 

//main.js
const surveyData = {
  mbti: 'ENFP',
  colorCode: '#ABCDEF',
  password: '0000'
};

fetch('https://learn.codeit.kr/api/color-surveys', {
  method: 'POST',
  body: JSON.stringify(surveyData) //JSON 문자열로 변환
});

JSON.stringify 메소드를 사용하면 자바스크립트 객체를 JSON 문자열로 변환 할 수 있다. 

마지막으로 데이터를 보낼 때는 Content-Type 헤더로 어떤 형식의 데이터를 보내는지 알려주는 것이 좋은데 headers 프로퍼티로 헤더까지 설정해주자.

//main.js
const surveyData = {
  mbti: 'ENFP',
  colorCode: '#ABCDEF',
  password: '0000'
};

fetch('https://learn.codeit.kr/api/color-surveys', {
  method: 'POST',
  body: JSON.stringify(surveyData),
  headers: {
    'Content-Type': 'application/json', //필요하다면 다른 헤더들도 아래 추가할 수 있다.
  },
});

이제 POST 리퀘스트를 보낼 준비는 끝났다. 리스폰스를 처리하는 코드만 작성해주면 되는데 GET 리퀘스트와 완전 동일하다.

//main.js
const surveyData = {
  mbti: 'ENFP',
  colorCode: '#ABCDEF',
  password: '0000'
};

const res = await fetch('https://learn.codeit.kr/api/color-surveys', {
  method: 'POST',
  body: JSON.stringify(surveyData),
  headers: {
    'Content-Type': 'application/json'
  }
});

const data = await res.json();
console.log(data);

POST 리퀘스트로 데이터를 생성하면 서버가 생성된 데이터를 리스폰스로 돌려주는 경우가 많은데 const data = await res.json(); 의 json 메소드로 리스폰스를 파싱해서 출력한다. 

결과창

코드를 실행해 보면 새로 생성된 설문 객체가 잘 돌아온다. 

실제 웹 개발을 하는 상황이라고 생각해 보면 사용자가 폼에 mbti, 좋아하는 색상 정보를 입력하면 그 값으로 surveyData 같은 객체를 만들고 그걸 POST 리퀘스트의 바디에 전달해서 설문 결과를 생성하는 것이다. 그리고 생성된 결과가 리스폰스로 돌아오면 그걸 화면에 보여주거나 할 수 있다.

쿼리 스트링

쿼리 스트링(Query String)
정의 URL에서 서버에 추가 정보를 전달하기 위해 사용되는 문자열로 주로 요청의 세부 사항을 지정하거나 필터링하는 데 사용된다.
쿼리 스트링은 URL의 마지막 부분에 위치하며, ?로 시작하고 여러 키-값 쌍으로 구성된다.
형태 ?key1=value1&key2=value2&key3=value3
구성요소 ?: 쿼리 스트링의 시작
key=value: 각 파라미터는 키와 값으로 구성
&: 여러 개의 파라미터를 구분하는 데 사용
예시 URL: https://example.com/search?query=apple&limit=10&page=2
query=apple: 검색어로 "apple"을 지정
limit=10: 최대 10개의 결과를 요청
page=2: 두 번째 페이지의 결과를 요청
사용용도 검색 필터링: 특정 조건에 맞는 데이터를 요청할 때 사용
페이지네이션: 데이터 목록을 여러 페이지로 나누어 요청할 때 유용
정렬 및 필터링: 반환되는 데이터를 정렬하거나 필터링하는 조건을 설정할 수 있다.

패스 파라미터와 쿼리 파라미터

  패스 파라미터 (Path Parameter) 쿼리 파라미터 (Query Parameter)
정의 URL의 경로에 포함되어 특정 리소스를 식별하는 데 사용되는 파라미터이다. URL의 끝에 추가되어 서버에 추가 정보를 전달하는 파라미터이다.
형태 URL 경로의 일부로 위치하며, /로 구분된다. URL의 마지막 부분에 위치하며, ?로 시작한다.
구성요소 https://example.com/resource/:id
여기서 :id는 패스 파라미터로, 특정 리소스의 고유 식별자를 나타낸다.
?key1=value1&key2=value2
여기서 key1과 key2는 파라미터의 이름이며, 각각의 값은 value1과 value2이다.
예시 URL: https://example.com/users/123
123: 사용자 ID로, 특정 사용자를 나타낸다.
URL: https://example.com/search?query=apple&limit=10&page=2
query=apple: 검색어로 "apple"을 지정한다.
limit=10: 최대 10개의 결과를 요청한다.
page=2: 두 번째 페이지를 요청한다.
offset: 데이터 요청 시 시작 위치를 지정 (위 예시엔 없다.)
  • 쿼리 스트링: URL의 끝부분에 위치하며, 여러 개의 쿼리 파라미터를 포함할 수 있는 문자열.
  • 쿼리 파라미터: 쿼리 스트링의 구성 요소로, 특정한 키와 값을 쌍으로 전달한다.

color-surveys에 리퀘스트를 보낼 때 뒤에 쿼리 스트링(Query string)을 추가할 수도 있는데 mbti, limit, offset이라는 쿼리 파라미터들을 사용할 수 있다.

해당 포스팅 필수 참고!

mbti로 특정 mbti에 해당하는 설문만 필터(filter)할 수 있고, limit과 offset은 페이지네이션(pagination 데이터 목록을 여러 페이지로 나누어 요청)에 사용된다.

//main.js
//쿼리 파라미터: mbti, limit, offset
const res = await fetch('https://learn.codeit.kr/api/color-surveys');
const data = await res.json();

console.log(data);

결과, next 의 마지막 부분을 잘 살펴보자

리퀘스트를 보내면 이런 결과가 돌아왔다.

  • count : 모든 데이터 개수
  • next : 다음 데이터를 받아오는 URL
  • previous : 이전 데이터를 받아오는 URL
  • results : 현재 페이지에 해당하는 데이터 배열

위 결과를 보면 전체 데이터는 54개가 있지만 결과는 10개밖에 없다. 

next: 'https://learn.codeit.kr/api/color-surveys/?offset=10&limit=10',

이 부분에서 offset=10&limit=10 이 부분 때문이다. offset은 데이터 몇개를 건너뛰고 요청할 것(offset=10은 11번째 항목부터 시작하여 데이터를 가져오겠다는 뜻)인지, limit은 데이터 몇개를 요청할 것인지를 뜻한다. 

지금 결과에서 10개를 받았으니 다음에는 처음에서부터 10개를 건너뛰고 10개를 받아올 것이다. 그래서 next 값이 이런 URL인 것이다. 이 URL로 리퀘스트를 보내보면

const res = await fetch('https://learn.codeit.kr/api/color-surveys/?offset=10&limit=10');
const data = await res.json();

console.log(data);

next와 previous 값이 바뀌었고 다음 10개의 데이터가 잘 출력되는것을 확인 할 수 있다.

이렇게 작성하면 mbti가 ISTJ인 설문 결과만 돌아오게 할 수도 있다.

const res = await fetch('https://learn.codeit.kr/api/color-surveys/?mbti=ISTJ');
const data = await res.json();

console.log(data);

쿼리 스트링을 직접 작성하지 않고 URL이라는 객체를 사용할 수도 있다.

const url = new URL('https://learn.codeit.kr/api/color-surveys'); //먼저 이렇게 URL 객체를 만들고
//객체의 searchParams라는 프로퍼티에 쿼리 파라미터를 추가하면 된다.
url.searchParams.append('offset', 10);
url.searchParams.append('limit', 10);

const res = await fetch(url); //이 부분은 url로 입력해주고 코드를 실행해보면
const data = await res.json();

console.log(data);

 

이전처럼 offset=10, limit=10 결과가 잘 돌아온다. 


//쿼리 파라미터: mbti, limit, offset
const res = await fetch('https://learn.codeit.kr/api/color-surveys');
const data = await res.json();

console.log(data);

GET api/color-surveys는 여러 설문 객체를 가져오는데 쓰이고, GET api/color-surveys/id는 설문 객체 하나를 가져오는데 쓰인다. 만약 id 5에 해당하는 객체 하나만 가져오고 싶다면 'https://learn.codeit.kr/api/color-surveys/5' 로 작성해주면 id : 5에 해당하는 설문 객체 하나만 돌아온다.

사용 할 API : Color Survey API MBTI 컬러 설문

//package.json
{
  type:"module"
}

import, export 문법과 모듈 최상위 레벨에서 await을 사용할 수 있도록 type을 module로 설정해 준다.

우선 fetch로 컬러 설문 목록을 가져와 보자.

fetch('https://learn.codeit.kr/api/color-surveys');

fetch 함수 안에 리퀘스트(웹 브라우저가 서버에 요청)를 보낼 URL을 쓰면 된다. fetch는 기본적으로 get 리퀘스트를 보내고 promise를 리턴한다. 시간이 지나고 리스폰스(서버가 보내주는 응답)가 도착하면 promise는 fulfilled 상태가 되고, 리스폰스를 결과값으로 갖게된다. 그래서 리스폰스를 가져오려면 await문을 써줘야 하는것이다.

const res = await fetch('https://learn.codeit.kr/api/color-surveys');

console.log(res); //리스폰스 출력

출력된 결과, 이 밑으로도 내용이 쭉 있다.

조금 복잡한 결과가 출력됐는데, 리스폰스에는 다양한 정보가 있다. 이중에서 가장 많이 사용하는 것은 상태코드, 헤더와 바디 내용이다.

const res = await fetch('https://learn.codeit.kr/api/color-surveys');
console.log('Status Code:');
console.log(res.status); //상태코드
console.log('Headers:');
console.log(res.headers); //헤더

상태코드 200과 헤더의 길고 긴 정보들. 이 중 content-type value: application/json을 기억하자!

이렇게 status와 headers로 프로퍼티로 접근할 수 있다. 반면 body는 내용이 일반 문자열이라면 텍스트 메소드, json 문자열이라면 json 메소드를 써야한다. 위의 헤더 내용 중 content-type value: application/json을 확인 할 수 있는데 사용할 API가 json 문자열을 돌려준다는 의미이기 때문에 json 메소드를 사용하면 된다.

//바디 내용은 단순히 프로퍼티로 가져올 수 없고 메소드를 사용해야 한다.
const res = await fetch('https://learn.codeit.kr/api/color-surveys');
const data = await res.json();
console.log(data);

json 메소드 역시 promise를 리턴하는 비동기 함수이기 때문에 앞에 await을 넣어줘야한다. json 메소드는 바디의 json 문자열을 읽어서 자바스크립트 객체로 변환해 준다.

리스폰스의 바디 내용이 잘 출력된다.

실제 설문 데이터 외에도 count나 next, previous 같은 필드들이 있는데, count는 모든 데이터 갯수, next는 다음 데이터를 받아오는 URL, previous는 이전 데이터를 받아오는 URL, 그리고 results는 현재 페이지에 해당하는 데이터 배열이다. (자세한건 다음 포스팅 참조)

데이터 변수에 할당된 이 결과는 자바스크립트 객체이기 때문에 디스트럭처링(Destructuring) 같은 문법을 사용해서 프로퍼티들을 쉽게 가져올 수 있다.

예를 들어 설문 목록에 해당하는 results 프로퍼티를 가져와서 첫번째 설문 객체의 프로퍼티들을 출력해본다면,

const res = await fetch('https://learn.codeit.kr/api/color-surveys');
const data = await res.json();

const { results } = data;
const survey = results[0];
const{ id, mbti, colorCode, createAt, updateAt } = survey;
console.log(id, mbti, colorCode, createAt, updateAt);

이렇게 프로퍼티들이 잘 출력된다. 


총 정리

const res = await fetch('URL');
//fetch: 아규먼트로 URL을 전달

// 리스폰스 상태 코드
res.status;

// 리스폰스 헤더
res.headers;

// 리스폰스 바디
await res.json(); // JSON 문자열을 파싱해서 자바스크립트 객체로 변환
await res.text(); // 문자열을 그대로 가져온다.

fetch와 json 함수 모두 promise를 리턴하기 때문에 앞에 await을 써줘야 한다.

실무에서는 여러개의 promise 객체를 다뤄야할 일도 종종 발생한다. 

데이터를 바로 출력하지 않고 배열에 저장해두었다가 모든 데이터가 저장되고 나서 배열을 출력해보도록 하자.

async function getEmployee(id) {
  const response = await fetch('https://learn.codeit.kr/api/employees/${id}');
  const data =  await response.json();
  return data;
};

for (let i = 1; i < 11; i++) {
  const employee = await getEmployee(i); //함수 바깥에서 값을 받아오려면 await문을 써야한다.
}

(for문) 함수 바깥에서 값을 받아오려면 await 문을 써야한다. 하지만 await이 있으면 getEmployee 함수가 완전히 끝날 때까지 기다린 후에 다음줄로 넘어가기 때문에 리퀘스트를 보내고 파싱하는 작업을 순차적으로 하게된다. 이럴 때 promise.all 메소드를 활용할 수 있다. 

Promise.all은 여러 promise를 동시에 기다릴때 사용한다.

async function getEmployee(id) {
  const response = await fetch('https://learn.codeit.kr/api/employees/${id}');
  const data =  await response.json();
  return data;
};
//promises 배열에 추가되는 것은 promise 객체이다.
const promises = []; //일단 이렇게 promises라는 배열을 만들고

for (let i = 1; i < 11; i++) {
  promises.push(getEmployee(i)); //getEmployee의 결과를 await하지않고 바로 배열에 추가
}

이렇게 하면 await을 하지 않기 때문에 리퀘스트를 거의 병렬적으로 보내게된다. (promises 배열에 추가되는 것은 promise 객체이다.)

async function getEmployee(id) {
  const response = await fetch('https://learn.codeit.kr/api/employees/${id}');
  const data =  await response.json();
  return data;
};

const promises = []; 

for (let i = 1; i < 11; i++) {
  promises.push(getEmployee(i));
}

Promise.all(promises); //추가

Promise.all(promises); 를 풀어서 작성해보자면 이렇게 된다.

Promise.all([p1,     p2,     p3,     p4,     p5,     ...]);

처음에는 배열에 있는 promise들이 모두 pending 상태일 것이다.

pending 상태

Promise.all 메소드도 promise를 리턴하는데 처음에는 pending 상태이다가 아규먼트로 전달된 promise들이 모두 fulfilled 상태가 되면 Promise.all도 fulfilled 상태가 되고, 

각 promise의 성공 결과값들로 이루어진 배열을 성공 결과값으로 갖게된다. 지금 쓰고 있는 예시의 경우 직원 데이터로 이루어진 배열이 성공 결과값이 될것이다.

반대로 아규먼트로 전달한 promise 중 하나라도 rejected 상태가 되면 Promise.all도 rejected 상태가 되고 rejected된 promise의 오류를 결과값으로 갖게된다.


Promise.all도 결국 promise 객체를 리턴하기 때문에 await이나 then 메소드를 이용할 수 있다.

async function getEmployee(id) {
  const response = await fetch('https://learn.codeit.kr/api/employees/${id}');
  const data =  await response.json();
  return data;
};

const promises = []; 

for (let i = 1; i < 11; i++) {
  promises.push(getEmployee(i));
}

const employees = await Promise.all(promises); //await 이용
console.log(employees);

await을 이용해서 성공 결과값, 즉 직원 배열을 가져왔다. 위 코드를 실행해보면 직원 코드가 잘 출력된다.


Promise.all도 try catch문을 이용해서 오류를 처리할 수도 있다.

async function getEmployee(id) {
  const response = await fetch(`https://learn.codeit.kr/api/employees/${id}`);
  const data = await response.json();
  return data;
}

const promises = [];

for (let i = 1; i < 11; i++) {
  promises.push(getEmployee(i));
}

let employees; //try문 바깥에서 선언

try {
  employees = await Promise.all(promises);
} catch (error) {
  console.log(error);
}

console.log(employees);

getEmployee 변수를 try문 바깥에서도 사용할 수 있도록 try문 바깥에서 선언했다. 그러면 Promise.all 부분에서 오류가 나도 try catch문이 오류를 잡아준다.

promise를 활용하는 비동기 작업 여러개를 병렬적으로 처리하고 싶을 땐 Promise.all 메소드를 사용하면 된다. (여러 비동기 작업을 한꺼번에 처리)

then 메소드와 프로미스 체인을 사용할 때는 catch와 finally 메소드를 사용할 수 있다.

fetch('https://learn.codeit.kr/api/employees')
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.log('Error!')) //코드 추가
  .finally(() => console.log('Finished')); //코드 추가
  
console.log('Task 2');
  
console.log('Task 3');

//위 코드는 아래 코드와 동일하다.
//export async function printEmployees() {
//  try {
//    const response = await fetch('https://learn.codeit.kr/api/employees');
//    const data = await response.json();
//    console.log(data); 
//  } catch (error) {
//    console.log('Error!');
//  } finally {
//    console.log('Finished');
//  }
//};

//printEmployees();

//console.log('Taks 2');
//console.log('Taks 3');

catch 메소드는 프로미스 체인에서 발생하는 오류를 잡아서 콜백을 실행해주고, finally 메소드는 프로미스 체인에서 어떤 일이 발생하든 끝에 콜백을 실행해준다. 코드를 실행해보면

Task 2
Task 3
[  
  {
    id: 1,
    name: 'Alice',
    email: 'alice@codeitmall.kr',
    department: 'engineering'
  },
...
]
Finished

Task 2와 3이 먼저 출력되고, 직원 데이터가 출력된 다음에 Finished 가 출력된 후 종료된다.

만약 주소가 잘못된 주소라면?

fetch('https://learn.codeit.krrrrrrrrrrrrrr/api/employees') //잘못된 주소 입력
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.log('Error!')) 
  .finally(() => console.log('Finished')); 
  
console.log('Task 2');
  
console.log('Task 3');
Task 2
Task 3
Error!
Finished

출력되는 순서는 이전과 똑같은데 이전에 출력됐던 직원 데이터 대신 Error!가 출력된다.


1. fetch 함수와 then, catch, finally 메소드는 모두 promise를 리턴한다.

2. 그리고 fetch 함수가 실행되는 동안 프로미스 체인 아래에 있는 코드를 실행한다.

3. fetch를 할 때 잘못된 주소를 적었기 때문에 오류가 발생할 것이다. 그래서 첫번째 promise는 rejected 상태가 되고, 결과값으로 오류를 갖게 된다.

4. then 메소드는 앞선 promise가 fulfilled 상태가 되야 등록된 콜백을 실행하는데, rejected 상태이기 때문에 아무 콜백도 실행하지 않는다. (뒤에 있는 promise는 이전 promise와 똑같은 상태와 결과값을 갖게된다.)

그러면 다음 then에 연결되어 있는 promise도 당연히 rejected가 된다.

5. catch 메소드는 이전 promise 상태가 rejected 상태가 되면 promise의 결과값을 가지고 콜백을 실행하는데 then의 반대라고 생각하면 된다. 
6. catch문의 콜백을 실행하고(Error! 출력) 

6-1 콜백이 promise를 리턴하면 catch 메소드가 리턴하는 promise도 동일한 상태와 결과값을 갖게 되고
6-2 콜백이 일반값을 리턴하면 catch 메소드가 리턴하는 promise는 fulfilled 상태가 되고 리턴 값을 결과값으로 갖게된다. (지금은 console.log를 리턴하고 있는데 console.log는 undefined를 리턴한다.)
7. promise가 fulfilled상태가 되고 undefined를 결과값으로 갖게 된다.

catch에서 오류를 이미 처리했기 때문에 콜백이 잘 실행되면 catch가 리턴하는 promise는 rejected가 아닌 fulfilled 상태를 갖는다.

8. 마지막으로 finally 메소드는 앞선 promise의 결과가 결정(fullfilled 혹은 rejected 상태)되면 등록된 콜백을 실행한다. 그래서 오류가 발생하든 안하든 항상 finally 콜백은 실행된다. 

fianlly 메소드의 등록된 콜백 실행

9. 그리고 이번에도 finally가 리턴한 promise는 fulfilled 상태가 되고, undefined를 결과값으로 갖게된다.

‼️결론

  • promise 객체의 then, catch, finally 메소드를 연결해서 프로미스 체인을 만들 수 있다. 
  • then 안에는 앞에 작업이 성공했을 때 실행할 콜백을 등록하면 된다.
  • catch 안에는 앞서는 프로미스 체인 어딘가에서 오류가 났을 때 실행할 콜백을 등록하면 된다.
  • 오류가 어디서 나든 rejected 상태의 promise가 계속 전파되는 원리 때문에 catch 메소드가 오류를 처리할 수 있다.
  • 그래서 catch 메소드는 보통 then 이후에 사용한다.
  • finally는 앞 선 promise의 결과가 어떻든 해결이 되면(더 이상 pending 상태가 아니면) 안에 있는 콜백을 실행한다. 
  • 프로미스 체인의 결과와 상관 없이 항상 실행되어야 하는 코드가 있다면 finally에 등록한다.
  • 프로미스 체인 어디에서든 결과를 기다리는 도중에는 이후에 있는 코드를 실행하고, 결과가 나오면 다시 체인으로 돌아오기 때문에 코드를 비동기적으로 처리할 수 있다.

'코린이 개념잡기 > 비동기 자바스크립트' 카테고리의 다른 글

리퀘스트: fetch 기본 문법  (0) 2024.12.25
Promise.all()  (1) 2024.12.25
.then() 메소드  (0) 2024.12.25
promise rejected 상태 이해하기  (0) 2024.12.25
오류 처리하기 (try catch)  (1) 2024.12.23

+ Recent posts