REST API 통신을 이해하기 위한 주요 JavaScript 문법

jellykelly·2025년 7월 22일

JavaScript

목록 보기
4/4
post-thumbnail

REST API를 호출할 때 자주 접하게 되는 JavaScript 문법과 패턴을 공부 겸 정리해 보았습니다.

1. 비동기 처리의 이해: Promise

  • 비동기 작업의 결과를 나중에 받아오기 위한 객체입니다. Promise는 비동기 연산이 완료된 이후에 성공 시 값을, 실패 시 이유(에러)를 처리할 수 있도록 promise를 반환합니다
  • resolve는 성공, reject는 실패를 나타냅니다.
  • 상태(state)는 세 가지로 구분됩니다:
    1. 대기(pending) – 아직 완료도 실패도 아닌 초기 상태
    2. 이행(fulfilled) – 작업이 성공적으로 완료된 상태
    3. 거부(rejected) – 작업이 실패된 상태

const promise = new Promise((resolve, reject) => {
  // (1) 비동기 작업 실행…
  if (/* 성공 */) {
    resolve('작업 결과 데이터');    // fulfilled
  } else {
    reject(new Error('에러 사유'));  // rejected
  }
});
// 지금은 pending 상태, 나중에 resolve/reject 호출에 따라 settled 됩니다.

promise
  .then(value => {
    console.log('이행 결과:', value);
  })
  .catch(error => {
    console.error('거부 이유:', error);
  });

.then() 메서드

  • 이행(onFulfilled) 콜백거부(onRejected) 콜백을 등록해, Promise가 완료된 뒤 실행할 로직을 연결합니다
  • .then() 자체도 항상 새로운 Promise를 반환하므로, 이어서 .then()을 체인(chain) 형태로 연결할 수 있습니다
// 사용 예시

fetch('https://api.example.com/users')       // Promise 반환 (pending)
  .then(response => response.json())         // ① 응답을 JSON으로 파싱
  .then(data => {
    console.log(data);                       // ② 파싱된 데이터를 처리
  })
  .catch(error => {
    console.error(error);                    // ③ 중간에 발생한 에러 처리
  });

2. async/await로 비동기 코드 가독성 높이기

Promise 기반 코드를 더 직관적으로 작성할 수 있는 문법입니다.

  • async 함수
    • 항상 Promise를 반환하며, 내부에서 return한 값은 자동으로 Promise.resolve()로 래핑됩니다
    • async 함수의 반환값 자동 래핑(Promise.resolve)
      • async 키워드를 붙여 선언한 함수는 항상 Promise 객체를 반환합니다.
      • 함수 내부에서 return 값;을 사용하면, 마치 아래 코드처럼 자동으로 Promise.resolve(값)을 반환하는 효과를 냅니다.
    // 암묵적 래핑 예시
    async function foo() {
      return 123;  
     // → 내부적으로는 return Promise.resolve(123);과 같습니다.
    }
        
    foo().then(value => {
      console.log(value);  // 123
    });
        
    // 명시적 래핑 예시
    function fooExplicit() {
      return Promise.resolve(123);
    }
        
    fooExplicit().then(value => {
      console.log(value);  // 123
    });

    async function foo() {
      return 123;
      // → 내부적으로 Promise.resolve(123)을 반환합니다.
    }
    
    foo().then(value => console.log(value)); // 123
  • await 키워드
    • Promise가 이행될 때까지 함수 실행을 일시 중단
    • 거부 시 throw를 발생시켜 catch 블록으로 전달
async function fetchUsers() {
  try {
    const response = await fetch('https://api.example.com/users');
    if (!response.ok) {
      // HTTP 상태 코드가 200~299가 아니면 예외 처리 추가
      throw new Error(`HTTP 에러: ${response.status}`);
    }
    const users = await response.json(); // JSON 파싱 대기
    return users;
  } catch (error) {
    console.error('사용자 목록 로드 실패:', error);
    throw error; // 필요 시 다시 던져 호출 측에서 추가 처리
  }
}

// 호출 예
fetchUsers()
  .then(users => console.log('사용자 목록:', users))
  .catch(err => console.error('최종 에러 처리:', err));

3. Fetch API로 HTTP 요청 보내기

  • 브라우저에 내장된 Promise 기반 API로, HTTP 요청을 간편하게 보낼 수 있습니다.
  • 간단한 문법과 유연한 옵션 설정이 특징입니다.
  • .then(), async/await와 함께 사용합니다.

GET 요청 예제 (async/await 버전)

async function getUsers() {
  try {
    const res = await fetch('https://api.example.com/users', {
      method: 'GET',
      headers: {
        'Accept': 'application/json'
      }
    });
    if (!res.ok) {
      throw new Error(`HTTP 에러: ${res.status}`); // 상태 코드 검사 추가
    }
    const users = await res.json();
    console.log('사용자 데이터:', users);
  } catch (error) {
    console.error('네트워크 또는 파싱 오류:', error);
  }
}

getUsers();

POST 요청 예제 (JSON 전송)

async function createPost(title, content) {
  try {
    const payload = { title, content };
    const res = await fetch('https://api.example.com/posts', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'  // JSON 전송을 위한 필수 헤더
      },
      body: JSON.stringify(payload)          // 객체를 문자열로 변환
    });
    if (!res.ok) {
      throw new Error(`HTTP 에러: ${res.status}`);
    }
    const result = await res.json();
    console.log('생성된 게시글:', result);
  } catch (error) {
    console.error('게시글 생성 실패:', error);
  }
}

createPost('제목 예시', '내용 예시');

4. 에러 처리 패턴: try/catch, .catch(), .finally()

  • 비동기와 동기 코드 모두에 적용 가능한 예외 처리 구조입니다.
  • try 블록 안에서 동기적 예외(throw)나, await로 기다린 Promise 거부(reject)가 발생하면 즉시 catch 블록으로 넘어갑니다.
  • .catch()는 Promise에서 발생한 에러를 잡습니다.
  • .finally() 블록을 쓰면 성공·실패와 상관없이 항상 마지막에 실행됩니다.

Promise 체인에서

fetch('/api/data')
  .then(res => {
    if (!res.ok) throw new Error(`상태 코드: ${res.status}`);
    return res.json();
  })
  .catch(err => console.error('파싱 오류 또는 네트워크 오류:', err))
  .finally(() => console.log('작업 완료'));

async/await 내부에서

async function loadData() {
  try {
    const res = await fetch('/api/data');
    if (!res.ok) throw new Error(`HTTP 에러: ${res.status}`);
    const data = await res.json();
    console.log(data);
  } catch (error) {
    console.error('데이터 로드 실패:', error);
  } finally {
    console.log('loadData() 종료');
  }
}

loadData();

5. 전통 방식: XMLHttpRequest (XHR)

Fetch 이전에 쓰이던 콜백 기반 API로, HTTP 세부 처리 단계 (준비 → 전송 → 응답)를 직접 제어할 수 있습니다.

const xhr = new XMLHttpRequest();                         // ① 객체 생성
xhr.open('GET', 'https://api.example.com/users');         // ② HTTP 메서드·URL 설정
xhr.onreadystatechange = () => {                         
  if (xhr.readyState === XMLHttpRequest.DONE) {
    if (xhr.status === 200) {
      const data = JSON.parse(xhr.responseText);          // ③ JSON 파싱
      console.log('데이터:', data);
    } else {
      console.error('상태 코드 오류:', xhr.status);
    }
  }
};
xhr.onerror = () => console.error('네트워크 오류 발생');      // ④ 네트워크 에러
xhr.send();                                               // ⑤ 요청 전송

6. 자주 쓰이는 문법 요약

6.1 Arrow Function (화살표함수)

  • 짧은 문법의 함수 표현식이며, this 바인딩이 주변 스코프와 같습니다.
  • Promise 콜백이나 짧은 함수에 주로 사용됩니다.
// 일반 함수
fetch(url).then(function(response) {
  return response.json();
});

// Arrow 함수
fetch(url).then(response => response.json());

6.2 Template Literal (백틱, ${})

  • 문자열 내에서 변수·표현식을 ${}로 간편하게 삽입합니다.
  • 여러 줄 문자열도 지원합니다.
const userId = 42;
const url = `https://api.example.com/users/${userId}`;    // 변수 삽입
console.log(`요청 URL: ${url}`);

6.3 Destructuring Assignment (구조 분해 할당)

  • 배열이나 객체에서 필요한 값을 간단히 추출합니다.
// 객체 분해
const user = { id: 1, name: 'Alice', age: 30 };
const { name, age } = user;                              // name과 age만 변수로 추출
console.log(name, age);

// 배열 분해
const arr = [10, 20, 30];
const [first, , third] = arr;                             // 두 번째 요소는 건너뜀
console.log(first, third);

6.4 Spread & Rest (스프레드 연산자 & 레스트 매개변수)

  • Spread (...obj or ...arr): 배열·객체를 펼쳐서 복사하거나 합칠 때 사용
  • Rest (function(...args) or const [a, ...rest]): 나머지 요소를 한 곳에 모아 배열 또는 객체로 받을 때 사용

Spread 연산자 (...)

  • Spread는 “펼치다”라는 뜻으로, 배열이나 객체를 개별 요소(프로퍼티)로 분해하여 삽입할 때 사용합니다.
  • 기존 값을 복사하거나, 여러 값을 하나로 합치면서 중첩 없이 간결하게 표현할 수 있습니다.
**// 배열에서의 사용 예제**

const arr1 = [1, 2, 3];
// 배열 복사
const copy = [...arr1];
// 👉 copy는 [1, 2, 3] (arr1과 별개 메모리)

/* 출력 확인 */
console.log('원본:', arr1);
console.log('복사본:', copy);

// 배열 합치기
const arr2 = [4, 5];
const merged = [...arr1, ...arr2];
// 👉 merged는 [1, 2, 3, 4, 5]

console.log('합쳐진 배열:', merged);
// 객체에서의 사용 예제

const defaults = { method: 'GET', headers: { Accept: 'application/json' } };
const options  = { headers: { 'Content-Type': 'application/json' }, cache: 'no-cache' };

// 객체 병합 (뒤에 나온 프로퍼티가 앞을 덮어씀)
const config = { ...defaults, ...options };
/*
  config는 {
    method: 'GET',
    headers: { 'Content-Type': 'application/json' },  // defaults.headers는 덮어써짐
    cache: 'no-cache'
  }
*/
console.log('병합 결과:', config);

// 프로퍼티 추가·수정
const updated = { ...config, method: 'POST', timeout: 5000 };
/*
  updated는 {
    method: 'POST',    // 원래 method GET에서 POST로 수정
    headers: { 'Content-Type': 'application/json' },
    cache: 'no-cache',
    timeout: 5000      // 새로 추가된 프로퍼티
  }
*/
console.log('수정 결과:', updated);

Rest 파라미터 (...)

  • Rest는 “나머지”라는 뜻으로, 함수 매개변수 또는 분해 할당(de-structuring)에서 남은 값들을 배열이나 객체로 한 번에 모을 때 사용합니다.
// 함수 매개변수에서의 사용 예제

// 첫 번째 인자 url은 고정, 나머지 인자는 모두 middlewares 배열에 담김
function request(url, ...middlewares) {
  console.log('요청 URL:', url);
  console.log('미들웨어 개수:', middlewares.length);
  console.log('미들웨어 목록:', middlewares);
}

const mw1 = () => {}, mw2 = () => {}, mw3 = () => {};
request('/api/users', mw1, mw2, mw3);
/* 출력:
  요청 URL: /api/users
  미들웨어 개수: 3
  미들웨어 목록: [mw1, mw2, mw3]
*/
// 배열 분해할당에서의 사용 예제

const numbers = [10, 20, 30, 40, 50];

// 첫 번째 요소만 first에, 나머지는 restNums 배열에 모음
const [first, ...restNums] = numbers;
console.log('첫 번째:', first);     // 10
console.log('나머지 배열:', restNums); // [20, 30, 40, 50]
// 객체 분해할당에서의 사용 예제

const user = { id: 1, name: 'Alice', age: 30, city: 'Seoul' };

// id와 name은 각각 변수에, 나머지 프로퍼티는 others 객체에 모음
const { id, name, ...others } = user;
console.log('id:', id);       // 1
console.log('name:', name);   // Alice
console.log('others:', others); // { age: 30, city: 'Seoul' }

6.5 Default Parameters (기본 매개변수)

  • 함수 매개변수에 기본값을 지정해, 인자가 없거나 undefined일 때 기본값을 사용합니다.
function fn(x = 10) {
  console.log(x);
}
fn();  // 10

function fetchData(url, options = { method: 'GET' }) {
  return fetch(url, options);
}
fetchData('/api');   // options는 { method: 'GET' }

6.6 JSON Methods (JSON 메서드)

  • JSON.stringify: JS 객체를 JSON 문자열로 변환
  • JSON.parse: JSON 문자열을 JS 객체로 변환
const str = JSON.stringify({ a: 1 });
const obj = JSON.parse(str);
console.log(str)    // {"a":1}
console.log(obj)    // { a: 1 }

const bodyObj = { title: '안녕하세요', content: 'REST API 예제' };
const bodyText = JSON.stringify(bodyObj);                // 문자열화
fetch('/posts', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: bodyText,
})
  .then(res => res.json())
  .then(data => {
    // 서버 응답이 JSON이면 자동 파싱
    console.log(data);
  });

6.7 URLSearchParams (URL 쿼리 매개변수 처리 API)

  • 객체 형태로 쿼리 스트링을 생성하거나 파싱합니다.
  • URLSearchParamsURL의 쿼리 문자열(예: ?key1=값1&key2=값2)을 생성·조작·파싱할 수 있게 해 주는 Web API입니다.
const params = new URLSearchParams({ page: 1, limit: 20 });
const url = `https://api.example.com/items?${params.toString()}`;
fetch(url)
  .then(res => res.json())
  .then(data => console.log(data));

6.8 FormData (폼 데이터 전송 API)

  • 파일 업로드나 폼 전송 시 multipart/form-data 형태로 데이터 구성에 사용합니다.
const form = new FormData();
form.append('username', 'kim');
form.append('avatar', fileInput.files[0]);

fetch('/upload', {
  method: 'POST',
  body: form,           // Content-Type은 자동 설정
})
  .then(res => res.json())
  .then(result => console.log(result));

6.9 Optional Chaining (옵셔널 체이닝)

  • 중첩된 객체 프로퍼티 접근 시, 값이 null 또는 undefinedundefined를 반환하고 에러를 방지합니다.
const response = { data: { user: null } };
console.log(response.data.user?.name);                   // 에러 없이 undefined 반환

6.10 Nullish Coalescing (널 병합 연산자)

  • 좌측 피연산자가 null 또는 undefined일 때만 우측 값을 사용합니다.
const token = response.token ?? '토큰없음';
console.log(token);

6.11 Promise.all() (병렬 Promise 실행)

  • 여러 Promise를 병렬 실행하고, 모두 완료되면 결과를 배열로 반환합니다.
  • 하나라도 실패하면 전체가 실패합니다.
const req1 = fetch('/api/a').then(r => r.json());
const req2 = fetch('/api/b').then(r => r.json());

Promise.all([req1, req2])
  .then(([resA, resB]) => {
    console.log('A 결과:', resA);
    console.log('B 결과:', resB);
  })
  .catch(err => console.error('하나라도 실패:', err));
profile
Hawaiian pizza with extra pineapples please! 🥤

0개의 댓글