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로 에러를 처리하세요
다음 단계
JavaScript 독학 가이드 시리즈를 모두 완주하셨어요!
이제 뭘 해야 할까요?
- 프로젝트 만들기: To-Do 앱, 날씨 앱, 계산기 등 실제로 만들어보세요
- 프레임워크 배우기: React, Vue, 또는 Svelte를 시작해보세요
- Node.js: JavaScript로 서버도 만들 수 있어요
- TypeScript: JavaScript에 타입을 추가한 언어예요
JavaScript는 계속 발전하고 있어요. 꾸준히 공부하고 실습하면서 성장하세요!
JavaScript 독학 가이드 시리즈:
- 이전: JavaScript 독학 가이드 - ES6+ 최신 문법
- 시리즈 완료! 처음부터 다시 보려면: JavaScript 독학 가이드 - 소개 및 시작하기