JS의 비동기

🔍 1. 동기 vs 비동기가 뭔가요?

동기 처리 (Synchronous):

console.log("1번");
console.log("2번");
console.log("3번");

// 결과: 1번 → 2번 → 3번 (순서대로)
// 앞의 작업이 끝나야 다음 작업 실행

비동기 처리 (Asynchronous):

console.log("1번");

setTimeout(() => {
  console.log("2번 (3초 후)");
}, 3000);

console.log("3번");

// 결과: 1번 → 3번 → (3초 후) 2번
// 2번을 기다리지 않고 3번 먼저 실행!

⏰ 2. 왜 비동기가 필요할까?

// 만약 동기적으로만 처리한다면...
console.log("서버에 데이터 요청 시작");
// 여기서 3초 동안 멈춤... (서버 응답 기다림)
// 사용자는 3초 동안 아무것도 할 수 없음!
console.log("데이터 받음");
console.log("화면에 버튼 클릭 처리");

// 비동기로 처리하면...
console.log("서버에 데이터 요청 시작");
console.log("화면에 버튼 클릭 처리"); // 바로 실행!
// 서버 응답이 오면 그때 처리

🔄 3. 비동기 처리의 3가지 방법

방법 1: 콜백 (Callback) – 구식

function getData(callback) {
  setTimeout(() => {
    const data = "서버에서 받은 데이터";
    callback(data);  // 작업 완료 후 콜백 호출
  }, 2000);
}

getData((result) => {
  console.log(result);  // "서버에서 받은 데이터"
});

console.log("이건 먼저 실행됨");

콜백의 문제점 – 콜백 지옥:

// 단계별 작업이 필요한 경우
getUserData((user) => {
  getOrderData(user.id, (orders) => {
    getPaymentData(orders[0].id, (payment) => {
      processPayment(payment, (result) => {
        console.log("결제 완료!");
        // 중첩이 너무 깊어짐... 😵
      });
    });
  });
});

방법 2: Promise – 개선된 방법

function getData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true;
      
      if (success) {
        resolve("서버에서 받은 데이터");  // 성공
      } else {
        reject("에러 발생!");             // 실패
      }
    }, 2000);
  });
}

// Promise 사용법
getData()
  .then((result) => {
    console.log(result);  // 성공했을 때
  })
  .catch((error) => {
    console.log(error);   // 실패했을 때
  });

Promise 체이닝:

getUserData()
  .then((user) => {
    return getOrderData(user.id);
  })
  .then((orders) => {
    return getPaymentData(orders[0].id);
  })
  .then((payment) => {
    return processPayment(payment);
  })
  .then((result) => {
    console.log("결제 완료!");
  })
  .catch((error) => {
    console.log("어디선가 에러:", error);
  });

방법 3: async/await – 최신 방법 (제일 좋음)

async function processOrder() {
  try {
    const user = await getUserData();
    const orders = await getOrderData(user.id);
    const payment = await getPaymentData(orders[0].id);
    const result = await processPayment(payment);
    
    console.log("결제 완료!");
    // 마치 동기 코드처럼 보이지만 비동기!
  } catch (error) {
    console.log("에러:", error);
  }
}

processOrder();

🌐 4. fetch API로 실제 사용해보기

기본 fetch 사용법:

// Promise 방식
fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.log('에러:', error);
  });

// async/await 방식 (더 깔끔)
async function getPost() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.log('에러:', error);
  }
}

getPost();

여러 API 동시에 호출:

async function getMultipleData() {
  try {
    // 동시에 여러 요청 보내기
    const [usersResponse, postsResponse] = await Promise.all([
      fetch('https://jsonplaceholder.typicode.com/users'),
      fetch('https://jsonplaceholder.typicode.com/posts')
    ]);
    
    const users = await usersResponse.json();
    const posts = await postsResponse.json();
    
    console.log('사용자:', users);
    console.log('게시글:', posts);
  } catch (error) {
    console.log('에러:', error);
  }
}

POST 요청 보내기:

async function createPost() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        title: '새 게시글',
        body: '게시글 내용',
        userId: 1
      })
    });
    
    const newPost = await response.json();
    console.log('생성된 게시글:', newPost);
  } catch (error) {
    console.log('에러:', error);
  }
}

⚠️ 5. 자주 하는 실수들

실수 1: await 없이 사용

// ❌ 잘못된 방법
async function badExample() {
  const response = fetch('https://api.example.com/data');
  console.log(response); // Promise 객체가 출력됨 (데이터 아님!)
}

// ✅ 올바른 방법
async function goodExample() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  console.log(data); // 실제 데이터 출력
}

실수 2: 에러 처리 안 함

// ❌ 위험한 방법
async function risky() {
  const response = await fetch('https://wrong-url.com/data');
  const data = await response.json(); // 에러 발생 시 앱 크래시!
}

// ✅ 안전한 방법
async function safe() {
  try {
    const response = await fetch('https://api.example.com/data');
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.log('데이터를 가져올 수 없습니다:', error);
    return null;
  }
}

실수 3: 순차 처리 vs 병렬 처리 혼동

// ❌ 느린 방법 (순차 처리)
async function slow() {
  const user = await fetch('/api/user');     // 1초 기다림
  const orders = await fetch('/api/orders'); // 또 1초 기다림
  // 총 2초 소요
}

// ✅ 빠른 방법 (병렬 처리)
async function fast() {
  const [userResponse, ordersResponse] = await Promise.all([
    fetch('/api/user'),
    fetch('/api/orders')
  ]);
  // 동시에 요청해서 1초만 소요
}

🎯 6. 실제 사용 예시

class WeatherApp {
  constructor() {
    this.apiKey = 'your-api-key';
    this.baseUrl = 'https://api.openweathermap.org/data/2.5';
  }
  
  async getCurrentWeather(city) {
    try {
      this.showLoading();
      
      const response = await fetch(
        `${this.baseUrl}/weather?q=${city}&appid=${this.apiKey}&units=metric`
      );
      
      if (!response.ok) {
        throw new Error(`날씨 정보를 찾을 수 없습니다: ${response.status}`);
      }
      
      const weatherData = await response.json();
      this.displayWeather(weatherData);
      
    } catch (error) {
      this.showError(error.message);
    } finally {
      this.hideLoading();
    }
  }
  
  showLoading() {
    console.log('날씨 정보 로딩 중...');
  }
  
  hideLoading() {
    console.log('로딩 완료');
  }
  
  displayWeather(data) {
    console.log(`${data.name}: ${data.main.temp}°C, ${data.weather[0].description}`);
  }
  
  showError(message) {
    console.log('에러:', message);
  }
}

// 사용
const weatherApp = new WeatherApp();
weatherApp.getCurrentWeather('Seoul');

💡 핵심 정리:

1. 비동기가 필요한 이유:

  • 네트워크 요청, 파일 읽기 등 시간이 걸리는 작업
  • 사용자 경험 향상 (멈추지 않는 앱)

2. async/await가 최고:

  • 코드가 읽기 쉬움
  • 에러 처리가 간단 (try/catch)
  • Promise의 장점 + 동기 코드 같은 가독성

3. 꼭 기억할 것:

  • awaitasync 함수 안에서만 사용
  • 에러 처리는 필수 (try/catch)
  • 병렬 처리가 필요하면 Promise.all() 사용

4. fetch API 패턴:

async function apiCall() {
  try {
    const response = await fetch(url);
    if (!response.ok) throw new Error('Network error');
    const data = await response.json();
    return data;
  } catch (error) {
    console.log('Error:', error);
    returnnull;
  }
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *