JavaScript 입문 10편 - API 호출하기
※ 이 게시물은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

서버에서 데이터 가져오기, 파일 업로드, 타이머 등은 시간이 걸려요. 이런 작업을 비동기로 처리하지 않으면 브라우저가 멈춰버립니다. JavaScript에서 비동기 처리는 필수예요!
동기 vs 비동기
동기 (Synchronous)
한 작업이 끝나야 다음 작업 시작해요.
console.log("1");
console.log("2");
console.log("3");
실행 결과:
순서대로 1, 2, 3이 출력됩니다
비동기 (Asynchronous)
작업을 기다리지 않고 바로 다음으로 넘어가요.
console.log("1");
setTimeout(() => {
console.log("2");
}, 1000);
console.log("3");
실행 결과:
2는 1초 후에 출력됩니다. setTimeout은 비동기라서 기다리지 않고 바로 다음 코드를 실행해요
콜백 지옥
옛날엔 비동기를 콜백으로 처리했어요.
// 사용자 정보 가져오기 → 주문 내역 가져오기 → 상세 정보 가져오기
getUser(userId, (user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0].id, (details) => {
console.log(details);
});
});
});
이게 콜백 지옥이에요. 읽기도 어렵고 에러 처리도 복잡해요.
Promise
Promise는 비동기 작업의 결과를 나타내는 객체예요.
Promise 기본
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("성공!"); // 성공
// reject("실패!"); // 실패
}, 1000);
});
promise
.then(result => {
console.log(result); // "성공!"
})
.catch(error => {
console.log(error);
});
실행 결과:
Promise가 resolve되면 then이 실행됩니다
Promise 체이닝
function getUser(id) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id, name: "철수" });
}, 1000);
});
}
function getOrders(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{ id: 1, product: "노트북" }]);
}, 1000);
});
}
getUser(123)
.then(user => {
console.log(user);
return getOrders(user.id);
})
.then(orders => {
console.log(orders);
})
.catch(error => {
console.error(error);
});
실행 결과:
Promise 체이닝으로 순차적으로 비동기 작업을 처리합니다. 콜백 지옥보다 훨씬 읽기 쉽죠?

async/await
Promise를 더 쉽게 쓰는 문법이에요.
기본 사용법
// Promise 방식
function getData() {
return fetch("https://api.example.com/data")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
}
// async/await 방식
async function getData() {
try {
let response = await fetch("https://api.example.com/data");
let data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
비교:
async/await를 사용하면 비동기 코드를 동기 코드처럼 읽기 쉽게 작성할 수 있어요. await는 async 함수 안에서만 쓸 수 있어요
여러 비동기 작업
async function fetchUserData(userId) {
try {
let user = await getUser(userId);
let orders = await getOrders(user.id);
let details = await getOrderDetails(orders[0].id);
return { user, orders, details };
} catch (error) {
console.error("에러 발생:", error);
}
}
fetchUserData(123);
실행 결과:
각 작업이 순차적으로 완료되면서 총 3초가 소요됩니다
fetch API
서버에서 데이터를 가져오는 API예요.
GET 요청
async function getTodos() {
try {
let response = await fetch("https://jsonplaceholder.typicode.com/todos");
let todos = await response.json();
console.log(todos);
} catch (error) {
console.error("에러:", error);
}
}
getTodos();
실행 결과:
실제 API에서 할 일 목록 200개를 가져옵니다
POST 요청
async function createTodo(todo) {
try {
let response = await fetch("https://jsonplaceholder.typicode.com/todos", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(todo)
});
let data = await response.json();
console.log(data);
} catch (error) {
console.error("에러:", error);
}
}
createTodo({
title: "새로운 할 일",
completed: false,
userId: 1
});
실행 결과:
서버에 데이터를 전송하고 생성된 객체를 받습니다 (id가 자동으로 추가됨)
에러 처리
async function fetchData(url) {
try {
let response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP 에러! 상태: ${response.status}`);
}
let data = await response.json();
return data;
} catch (error) {
console.error("데이터 가져오기 실패:", error);
return null;
}
}
실행 결과 (에러 발생 시):
에러가 발생하면 catch 블록에서 처리하고 null을 반환합니다
Promise.all
여러 Promise를 동시에 실행해요.
async function getAllData() {
try {
let [users, posts, comments] = await Promise.all([
fetch("https://jsonplaceholder.typicode.com/users").then(r => r.json()),
fetch("https://jsonplaceholder.typicode.com/posts").then(r => r.json()),
fetch("https://jsonplaceholder.typicode.com/comments").then(r => r.json())
]);
console.log("사용자:", users);
console.log("게시글:", posts);
console.log("댓글:", comments);
} catch (error) {
console.error(error);
}
}
getAllData();
실행 결과:
Promise.all은 여러 비동기 작업을 병렬로 실행합니다. 모두 완료되면 결과를 배열로 반환해요. 하나라도 실패하면 전체가 실패해요
실전 예제
1. 사용자 검색
<!DOCTYPE html>
<html>
<body>
<input type="text" id="searchInput" placeholder="사용자 검색">
<div id="results"></div>
<script>
let input = document.querySelector("#searchInput");
let results = document.querySelector("#results");
input.addEventListener("input", async () => {
let query = input.value.trim();
if (!query) {
results.innerHTML = "";
return;
}
try {
let response = await fetch(`https://jsonplaceholder.typicode.com/users`);
let users = await response.json();
let filtered = users.filter(user =>
user.name.toLowerCase().includes(query.toLowerCase())
);
results.innerHTML = filtered
.map(user => `<p>${user.name} (${user.email})</p>`)
.join("");
} catch (error) {
results.innerHTML = "<p>에러 발생</p>";
}
});
</script>
</body>
</html>
2. 로딩 스피너
<!DOCTYPE html>
<html>
<head>
<style>
.loading {
display: none;
}
.loading.active {
display: block;
}
</style>
</head>
<body>
<button id="loadBtn">데이터 로드</button>
<div class="loading" id="loading">로딩 중...</div>
<div id="data"></div>
<script>
let loadBtn = document.querySelector("#loadBtn");
let loading = document.querySelector("#loading");
let dataDiv = document.querySelector("#data");
loadBtn.addEventListener("click", async () => {
loading.classList.add("active");
dataDiv.innerHTML = "";
try {
let response = await fetch("https://jsonplaceholder.typicode.com/posts");
let posts = await response.json();
dataDiv.innerHTML = posts
.slice(0, 10)
.map(post => `<h3>${post.title}</h3><p>${post.body}</p>`)
.join("");
} catch (error) {
dataDiv.innerHTML = "<p>데이터 로드 실패</p>";
} finally {
loading.classList.remove("active");
}
});
</script>
</body>
</html>
3. 날씨 앱 (실제 API 사용)
async function getWeather(city) {
const API_KEY = "your_api_key_here"; // OpenWeatherMap API 키
try {
let response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric&lang=kr`
);
if (!response.ok) {
throw new Error("도시를 찾을 수 없습니다");
}
let data = await response.json();
return {
city: data.name,
temp: Math.round(data.main.temp),
description: data.weather[0].description,
humidity: data.main.humidity
};
} catch (error) {
console.error("날씨 정보 가져오기 실패:", error);
return null;
}
}
// 사용
getWeather("Seoul").then(weather => {
if (weather) {
console.log(`${weather.city}: ${weather.temp}°C, ${weather.description}`);
}
});
4. 이미지 갤러리
<!DOCTYPE html>
<html>
<body>
<button id="loadPhotos">사진 로드</button>
<div id="gallery"></div>
<script>
let loadBtn = document.querySelector("#loadPhotos");
let gallery = document.querySelector("#gallery");
loadBtn.addEventListener("click", async () => {
try {
let response = await fetch("https://jsonplaceholder.typicode.com/photos");
let photos = await response.json();
gallery.innerHTML = photos
.slice(0, 20)
.map(photo => `
<div>
<img src="${photo.thumbnailUrl}" alt="${photo.title}">
<p>${photo.title}</p>
</div>
`)
.join("");
} catch (error) {
gallery.innerHTML = "<p>사진 로드 실패</p>";
}
});
</script>
</body>
</html>
자주 하는 실수
1. await 빠뜨리기
// 틀림
async function getData() {
let response = fetch(url); // Promise 객체 반환
console.log(response); // Promise 출력됨
}
// 맞음
async function getData() {
let response = await fetch(url);
console.log(response); // 실제 응답 출력됨
}
실행 결과 비교:
await를 빠뜨리면 Promise 객체 자체가 반환되고, await를 사용하면 실제 결과값을 받습니다
2. async 없이 await 사용
// 틀림
function getData() {
let data = await fetch(url); // 에러!
}
// 맞음
async function getData() {
let data = await fetch(url);
}
실행 결과:
await는 반드시 async 함수 안에서만 사용할 수 있습니다
3. 에러 처리 안 하기
// 나쁜 예
async function getData() {
let response = await fetch(url);
let data = await response.json();
return data;
}
// 좋은 예
async function getData() {
try {
let response = await fetch(url);
if (!response.ok) throw new Error("HTTP 에러");
let data = await response.json();
return data;
} catch (error) {
console.error("에러:", error);
return null;
}
}
실행 결과 비교:
에러 처리를 하지 않으면 예상치 못한 에러로 앱이 중단될 수 있습니다. 항상 try-catch로 에러를 처리하세요
운영자 실전 노트
실제 프로젝트 진행하며 겪은 문제
- fetch 후 await 빠뜨려서 Promise 객체 자체를 렌더링 → 모든 비동기 호출에 await 추가
- Promise 체이닝 중 에러 발생 시 앱 전체 중단 → try-catch로 각 비동기 블록 감싸고 fallback UI 표시
이 경험을 통해 알게 된 점
- async 함수는 항상 Promise를 반환하므로, 호출 시 await 또는 .then() 필요
- 에러 처리를 하지 않으면 Unhandled Promise Rejection으로 앱이 멈추므로, 반드시 try-catch 사용
다음 단계
JavaScript 독학 가이드 시리즈를 모두 완주했다!
이제 무엇을 해야 할까?
- 프로젝트 만들기: To-Do 앱, 날씨 앱, 계산기 등 실제로 만들어본다
- 프레임워크 배우기: React, Vue, 또는 Svelte를 시작한다
- Node.js: JavaScript로 서버도 만들 수 있다
- TypeScript: JavaScript에 타입을 추가한 언어다
JavaScript는 계속 발전하고 있다. 꾸준히 공부하고 실습하면서 성장한다.
JavaScript 독학 가이드 시리즈:
- 이전: JavaScript 독학 가이드 - ES6+ 최신 문법
- 시리즈 완료! 처음부터 다시 보려면: JavaScript 독학 가이드 - 소개 및 시작하기