보통 웹 개발을 할 때는 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);

설문 객체 생성 완료!

설문 객체도 잘 생성됐다. 

참여 동기 및 과정 소개

프론트엔드는 아니고 웹 퍼블리셔 국비과정을 수료했던 경험이 있다. 디자인 위주의 수업이었고, 취업을 목표로 하는 학원 같지는 않아서 아쉬운 점도 있었다. (수강생 중에 60대 할아버지 두 분이 계셨는데, 수업 시간에 신문을 읽거나 딴짓을 해도 제지하는 게 없어서 살짝 당황스럽기도 했다.) 그래도 디자인과 코딩 수업을 통해 나는 디자인보다 코딩 수업을 더 좋아한다는 점과 프론트엔드 개발자라는 직업의 매력을 알게 되었다. 아무래도 눈으로 보이는 작업물을 다루다 보니 성과가 보여서 더 뿌듯하고 좋았고, 덕분에 부트캠프에 도전하는 계기가 되었다.

첫 시작

첫 시작 땐 팀원들과의 조우가 많이 긴장됐었다. 낯을 많이 가리는 편은 아니지만 새로운 시작은 긴장 될 수밖에 없는것 같다. 우리팀은 6명으로 구성되어 있었다. (다른 팀은 5명씩도 있었던 듯)

팀 활동

월요일부터 토요일까지 매일 팀 미팅을 진행했다. 첫 시간에 팀장 선정과 멘토링 일정 협의, 미팅 시간을 정하고 간단하게 자기소개를 했다. 

더보기

우리팀은 

월요일부터 목요일은 16시 미팅, 금요일은 20시 미팅, 토요일은 13시에 미팅을 진행했고, 멘토링은 월, 목 17시에 진행했다.

그리고 데일리 팀미션이라고, 팀미팅 시간 때 매일 돌아가며 배운 내용을 물어보는 활동과 더불어 한 명씩 돌아가며 배운 내용 중 주제를 정해 문제를 내면 팀원들이 구두로 대답하는 활동을 했는데 추후에 있을 취업 대비 면접을 준비하는 느낌으로 진행했다.

데일리 팀 미팅 6번 중 2번은 멘토링 활동도 같이 하는데, 현직 프론트엔드 개발자 멘토님과 함께하는 시간이었다. 팀원들과 협의해서 시간을 정하고 디스코드에서 만났는데 팀원들이 서로 잘 맞아서 팀 미팅 및 멘토링이 끝나고도 1시간 동안 잡담을 나눈 날도 있었다. (마지막 멘토링 때는 멘토링시간이 끝나고 난 뒤에도 헤어지기 아쉬워🥲 추가로 1시간동안 잡담을 나눴다.)

매주 토요일에는 위클리 페이퍼라는 활동도 했다. 코드잇에서 월요일에 새로운 주제를 디스코드에 공지해주면 그 주 토요일 전까지 해당 주제에 대해 생각을 정리해 팀원들과 토요일에 해당 주제에 대해서 이야기를 나누는 시간을 가졌다.

배운내용

파트1에서 HTML, CSS, Git, 유닉스 커맨드, 반응형 웹 퍼블리싱, 자바스크립트 중급, 인터렉티브 자바스크립트, 모던 자바스크립트, 비동기, 리퀘스트 등을 배웠다. (와, 이렇게 나열하니 정말 많다.)

그중에서 가장 기억에 남는 건 Git 수업이었다. 이전에 학원에서 잠깐 해본 기억이 있는데, 너무 어려워서 포기할까 고민도 했었던 애증의 Git. 하지만 Git은 필수라서 꼭 이해하고 싶었고, 수업에서 흥미를 갖고 공부하려고 노력했다.

느낀 점

강의는 온라인 강의 기반으로, 기초적인 이론 설명을 많이 다뤘는데, 조금 더 심화된 내용이라던지 이해를 위해 다른 내용을 추가로 알아야 하는 것은 직접 찾아보며 공부해야 했다. 그래서 초심자인 나에겐 조금 어렵게 느껴졌다. 어려울 땐 확실히 힘들긴 하다. "왜 이해를 이정도밖에 못할까?" 하고 자책스러운 마음도 있고, 다른사람들은 다 잘 나가고있는데 나만 이러는게 아닐까 하는 걱정도 들기도 했다. 하지만 나같은 비전공자에 초심자인 사람들도 분명 있었고, 그들과 얘기하면서 많이 위로를 얻었다. 이기적인것 같지만 나만 힘든게 아니라는 위로는 나를 괜찮게 많들기도 하는것 같다.

어렵긴 했지만 심화된 내용을 알아가기 위해 스스로 공부하는 방법도 깨닫기도 했고, 힘들 때는 토요일 보충 수업도 듣고(지금 껏 매 보충수업 올 출석인건 안비밀😅), 구글링으로 추가 학습도 하면서 계속 발전해 나가려고 노력했다.

우리 팀은 전공자도 꽤 있었는데 그들이 해주는 말들이 나에게 도움이 되기도 했다. 
유닉스 커맨드 파트를 정리하면서 듣느라 진도를 나가는게 좀 더디기도 했고, 어려워서 힘들어 했는데 '술술 머리에 스치듯이 정보를 입력하라'는 조언도 있었고, '그래도 중요한 부분이긴 하니 정리하면서 듣는게 나중에 훨씬 도움이 많이 될거다' 라는 의견도 들었다. 그런 얘기를 듣고나니 진도를 더디게 나가긴 했지만 머리에 조금이라도 남기려는 노력을 한 내가 조금 뿌듯해졌다.

그 외에도 팀원들이 추천하는 인프런 강의, 추천 교재(모던 자바 Deep Dive 등), 요약본 파일 공유(그저 빛✨) 등 말 뿐만 아니라 현실적인 도움도 많은 힘이 되었다.

앞으로의 계획

파트 2에 대한 기대와 걱정이 반반 섞여있다. 파트1 팀원들과 정말 잘 맞아서, 이렇게 좋은 팀원이 또 있을지 걱정이 되는것도 사실이다. (멘토님도 우리 팀처럼 반응이 좋고 참여도 잘하는 팀은 오랜만에 만난다고 하셨다.)  아직은 리액트를 본격적으로 배우기 직전이라 개인적인 계획은 리액트를 배우고 나서 방향성을 정해야 할 것 같다. 멘토님께서도 프론트엔드의 99%는 리액트가 필수라고 말씀하시기도 하셨으니.. 물론 자바스크립트는 계속 공부해야할것같다. 외우는게 어려우니 '눈과 손가락에라도 익도록 반복한다!' 라는 생각이다.

아, 그리고 스프린트 미션이라고 개인과제 제출 미션이 있다. 제출 기한에 제한은 없지만 주어진 디자인을 가지고 코딩해서 제출하는건데 멘토님께서 미션 5는 취업 과제로도 많이 나오는 문제라 꼭 해봤으면 좋겠다고 하셨으니 다음 파트 때는 스프린트 미션을 좀 더 열심히 수행해볼까 한다. (지금은 미션 1만 진행한 상황이다.)

파트1을 함께 해준 동료들에게..

던지는 말 한마디가 너무 웃겼던 갓생사는 ENFP님, 다정다감하고 상황정리를 잘하던 분위기 메이커 INFP님,  조용하지만 잘 어우러져서 천방지축팀을 묵묵히 이끌어줬던 팀장 ISFJ님, 뭔가 범상치 않은 드립과 전공자 + 막내다운 싱싱한 두뇌로 고견을 아낌 없이 나누던 INTJ님, 조금 늦게 합류했지만 잘 적응하고 금새 다른 팀원들을 따라잡던 똑똑이막내 ISTJ님, 그리고 파트1엔 알려줄 것이 많이 없다고 아쉬워 하며 멘토링 시간 늘 알차게 진행해주시던 꼼꼼+깔끔이(a.k.a 알잘딱깔센의 의인화) INTJ멘토님

어느 한명 모남 없이 모두가 둥글둥글하게 잘 어우러져서 더 기억에 많이 남고 아쉬움이 큰 것 같다. 얼굴만 봐도 반가워지는 내적 친밀감이 쌓일 때 쯔음 끝나는 느낌이라 섭섭하지만 다음 만남을 위해서, 그리고 언제든 대화를 나눌 수 있는 채널이 있으니까 섭섭함은 잠시 접어두도록 하겠다. 모두 취업까지 힘내서 열심히 나아갔으면 좋겠습니다.(물론 나도!) ♥팀 화이팅! (멘토님은 다음에 만나는 팀이 멘토님을 많이 힘들게 하지 않길 기원합니다.🙇‍♀️ 하지만 우리팀보단 좋아하지 말아줬으면.. 아냐 그래도 멘토님이 행복하셨으면.. 그래도 우리팀 최고였으면..♾️)

사설 디스코드방에 멘토님을 초대하기 직전에 팀원들끼리 나눴던 대화. 거의 깡패와 다를게 없는것 같지만 착각이다.
사설 디스코드 방의 존재를 멘토링 시간에 밝히고 멘토님도 사설 디스코드 방으로 초대 했던 날
그동안 팀미팅에서 진행했던 팀 미션 주제

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

자바스크립트에서 비동기 코드를 잘 활용하려면 async, await과 then 메소드 둘 다 알고 있는 것이 좋다. 

//main.js
async function printEmployees() {
  const response = await fetch('https://learn.codeit.kr/api/employees');
  const data = await response.json();
  console.log(data);
}

오류처리는 하지 않는 printEmployees 함수를 then 문법으로 바꿔본다면,

const dataPromise = fetch('https://learn.codeit.kr/api/employees')
					  .then((response) => response.json());
dataPromise.then((data) => console.log(data));

이렇게 바꿀 수 있다. 살펴보자면,

1. fetch는 promise를 리턴한다. (1번째 줄)
 2. then은 promise 객체의 메소드이기 때문에 뒤에 이어서 쓸 수 있다.(2번째 줄)
3. 그리고 then메소드 자체도 promise를 리턴한다. (console.log(dataPromise)를 입력해보고 실행해보면 promise pending 상태가 나온다.)
4. 그래서 dataPromise 뒤에서 then 메소드를 붙일 수 있다. (3번째 줄)

then 메소드는 앞 선 비도기 작업이 완료되면 등록된 콜백을 실행해준다. 

1. 리스폰스가 돌아오면 (response) => response.json() 콜백을 실행하고
2. response.json이 완료되면 (data) => console.log(data) 콜백을 실행한다.

이렇게 해서 실행해봐도 직원 정보가 잘 출력되는 결과를 확인 할 수 있다. 위 코드를 조금 더 간결하게 쓸 수 도 있는데,

fetch('https://learn.codeit.kr/api/employees')
  .then((response) => response.json())
  .then((data) => console.log(data));

이렇게 작성할 수도 있다. then은 promise를 리턴하기 때문에 then 뒤에 바로 then을 또 붙일 수 있는 것이다. 

이렇게 promise 뒤에 메소드를 계속 연결해서 쓰는것을 프로미스 체인(Promise Chain)이라고 한다.

작동방식을 세세하게 살펴보자.

1. fetch 함수는 URL로 리퀘스트를 보내고 곧바로 promise를 리턴한다.
2. 그리고 이어서 then 메소드 들도 promise를 리턴한다.

3. 이 때 안에 있는 콜백을 실행하는건 아니고 일단 pending 상태의 promise를 리턴하는 것이다.
4. 기다리다가 리퀘스트가 완료되서 리스폰스가 돌아오면 첫번째 promise는 fulfilled 상태가 된다.

5. promise가 fulfilled 상태가 되면 then 메소드에 등록된 콜백이 실행된다.
6. 그리고 이 때 promise의 결과값을 콜백의 첫번째 파라미터로 전달한다. (그래서 리스폰스가 콜백으로 전달)

리스폰스가 콜백으로 전달

6-1. 만약 콜백이 promise를 리턴하면 then 메소드가 리턴한 promise도 동일한 상태와 결과값을 갖게 된다.

6-1예시

6-2. 콜백이 promise가 아닌 평범한 값을 리턴하면 then 메소드가 리턴한 promise는 fulfilled 상태가 되고, 리턴값을 결과값으로            갖게 된다.

6-2 예시

7. 지금의 경우에는 response.json을 리턴하니까 response.json이 fulfilled 상태가 되면 
8. then 메소드가 리턴한 promise도 fulfilled 상태가 된다.

9. fulfilled 상태가 되면 다음 then 메소드에 등록된 콜백이 실행되는데 파싱된 데이터가 첫번째 파라미터로 전달된다.

파싱된 데이터가 파라미터로 전달!

10. 이 콜백은 데이터를 출력하고 console.log를 리턴하는데 console.log는 undefined를 리턴하기 때문에 결국 콜백은 undefined를 리턴한다.

11. 그래서 마지막 promise는 fulfilled 상태가 되고, undefined 값을 결과로 갖게 된다.

결과

이 promise에 무언가를 이어서 하지 않을거기 때문에 마지막 promise에 대해서는 신경쓰지 않아도 된다.


프로미스 체인(Promise Chain)의 중요한점

  • 비동기 작업을 기다리는 동안에는 다른 코드를 먼저 실행한다.
fetch('https://learn.codeit.kr/api/employees')
  .then((response) => response.json())
  .then((data) => console.log(data));
  
console.log('Task 2'); //코드 추가
  
console.log('Task 3'); //코드 추가

예를 들어 이렇게 코드를 추가해 보면 결과는 Task 2와 3이 먼저 출력되고 직원 데이터가 그 뒤를 이어 출력된다. 

실행 순서를 되짚어보자면 fetch 함수가 promise를 리턴하고, 이 promise가 pending 상태인 동안 console.log('Task 2')와 console.log('Task 3')을 실행한다. 그리고 조금 기다린 후 fulfilled 상태가 되면 리스폰스를 가지고 첫번째 then 메소드의 콜백을 실행한다. 그러면 이 콜백은 다시 promise를 리턴하고, 이 promise가 fulfilled가 될때까지 나머지 코드를 먼저 실행하는데 이번엔 더 이상 실행할 코드가 없기 때문에 잠시 기다렸다가 fulfilled 상태가 되면 promise의 결과값, 파싱 결과로 두번째 then 메소드의 콜백을 실행한다. 그러면 이제 직원 데이터가 실행되고 프로그램이 종료된다.

promise는 세가지의 상태를 가질 수 있다. (반복학습!)

pending : 기다리고 있는 상태 / fulfilled : 작업이 성공적으로 완료된 상태 / rejected : 작업이 실패한 상태

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!');
    return; 
  } finally {
    console.log('Finished');
  }
};

비동기 코드를 실행하는 동안 오류가 나면 promise는 rejected 상태가 되고, 발생한 오류를 결과값으로 갖게 된다. 그리고 promise 앞에 await이 있을 때는 promise의 결과값인 오류를 throw 해준다. try문 안에서 오류가 throw(던지다라는 의미)됐기 때문에 오류를 잡아서 처리했던 것이다.

코드 흐름 살펴보기

  1. 메인 파일을 실행한다고 했을 때 일단 printEmployees 함수를 import하고 printEmployees 함수를 실행(main 파일의 3번째줄)해서
  2. 안에 있는 fetch를 실행(asyncFunction 파일의 try문 안에 fetch)한다.
  3. fetch는 promise를 리턴하는데 앞에 await이 있기 때문에 결과를 기다리는 동안 함수 밖으로 나가서(main 파일로 이동) 이후 작업(main 파일 5, 7번째 줄)을 실행한다.
  4. 그럼 이제 비동기 작업은 성공하거나 실패를 할텐데 성공하면 promise 상태는 fulfilled 상태로 바뀌고 비동기 작업의 성공 결과값을 갖게 된다. 실패를 하면 promise는 rejected상태로 바뀌고 비동기 작업에서 발생한 오류를 결과값으로 갖게 된다. 어쨌든 promise가 pending에서 fulfilled나 rejected 상태가 되면 다시 함수 안으로 돌아와서 코드를 실행(main 파일에서 asyncFunction 파일 3번째줄 로 다시 이동한 후 코드를 실행)한다.
  5. fulfilled 상태면 promise의 값을 꺼내서 리턴해주고, 지금처럼 rejected 상태면 promise의 값을 throw 한다. 이 코드는 try 문으로 감싸져 있기 때문에 안에서 오류를 throw해도 잡아서 처리를 해준다.
  6. catch문 안에서 리턴을 해도 finally에 이는 코드를 실행하고 함수가 종료된다.
Task 2
Task 3
Error!
Finished

promise와 await의 원리 덕분에 비동기 코드를 익숙한 동기 형태로 작성할 수 있다. 비동기 작업의 결과값을 변수에 할당해서 다음 줄에서 활용하고 오류가 발생할 것을 대비해 try catch문으로 감싸주면 익숙하게 비동기 코드를 작성 할 수 있다. (단, 비동기 코드가 실행되는 순서를 조금 신경 써주어야 한다.)

+ Recent posts