June 29, 2025

πŸ‘» Promise μ—λŸ¬ λ¬΄μ‹œν•˜κΈ°: μ‘°μš©ν•œ μ‹€νŒ¨μ™€ ν˜Όλž€μŠ€λŸ¬μš΄ UI

λΉ„λ™κΈ°μ²˜λ¦¬
μ—λŸ¬μ²˜λ¦¬
UX
μƒνƒœκ΄€λ¦¬
React

Summary

비동기 μž‘μ—…(Promise)μ—μ„œ λ°œμƒν•˜λŠ” μ—λŸ¬λ₯Ό λͺ…μ‹œμ μœΌλ‘œ μ²˜λ¦¬ν•˜μ§€ μ•ŠμœΌλ©΄, μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ 쑰용히 μ‹€νŒ¨ν•˜κ³  μ‚¬μš©μžμ—κ²Œ ν˜Όλž€μŠ€λŸ¬μš΄ UIλ‚˜ 잘λͺ»λœ μƒνƒœλ₯Ό 보여쀄 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” 디버깅을 μ–΄λ ΅κ²Œ ν•˜κ³  μ‚¬μš©μž κ²½ν—˜μ„ 크게 μ €ν•΄ν•©λ‹ˆλ‹€. λͺ¨λ“  Promiseμ—λŠ” .catch()λ₯Ό λΆ™μ΄κ±°λ‚˜ async/await κ΅¬λ¬Έμ—μ„œ try...catchλ₯Ό μ‚¬μš©ν•˜μ—¬ μ—λŸ¬λ₯Ό 작고, μ‚¬μš©μžμ—κ²Œ μ μ ˆν•œ ν”Όλ“œλ°±μ„ μ œκ³΅ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Why Wrong?

μ™œ 이 νŒ¨ν„΄μ΄ λ¬Έμ œμΈκ°€?

ν”„λ‘ νŠΈμ—”λ“œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ λ°±μ—”λ“œ API 호좜, 파일 읽기, μ• λ‹ˆλ©”μ΄μ…˜ μ§€μ—° λ“± 비동기 μž‘μ—…μ€ ν•„μˆ˜μ μž…λ‹ˆλ‹€. μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” μ΄λŸ¬ν•œ 비동기 μž‘μ—…μ˜ κ²°κ³Όλ₯Ό Promise 객체λ₯Ό 톡해 λ‹€λ£Ήλ‹ˆλ‹€. PromiseλŠ” 성곡(resolve) λ˜λŠ” μ‹€νŒ¨(reject) μƒνƒœλ₯Ό λ‚˜νƒ€λ‚΄λŠ”λ°, κ°œλ°œμžκ°€ reject μƒνƒœλ₯Ό 적절히 처리(catch)ν•˜μ§€ μ•ŠμœΌλ©΄ λ¬Έμ œκ°€ λ°œμƒν•©λ‹ˆλ‹€.

1. μ‘°μš©ν•œ μ‹€νŒ¨ (Silent Failure)

Promiseκ°€ rejectλ˜μ—ˆμŒμ—λ„ λΆˆκ΅¬ν•˜κ³  .catch() λ©”μ„œλ“œκ°€ μ—°κ²°λ˜μ–΄ μžˆμ§€ μ•ŠμœΌλ©΄, ν•΄λ‹Ή μ—λŸ¬λŠ” Unhandled Promise Rejection으둜 κ°„μ£Όλ©λ‹ˆλ‹€. λŒ€λΆ€λΆ„μ˜ λͺ¨λ˜ λΈŒλΌμš°μ €λ‚˜ Node.js ν™˜κ²½μ—μ„œλŠ” μ΄λŸ¬ν•œ μ—λŸ¬λ₯Ό μ½˜μ†”μ— 경고둜 좜λ ₯ν•˜μ§€λ§Œ, μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ‹€ν–‰ 흐름은 λ©ˆμΆ”μ§€ μ•Šκ³  κ³„μ†λ©λ‹ˆλ‹€. 개발자 도ꡬλ₯Ό 열어보지 μ•ŠμœΌλ©΄ μ΄λŸ¬ν•œ 였λ₯˜κ°€ λ°œμƒν–ˆλŠ”μ§€ μ•ŒκΈ° μ–΄λ ΅μŠ΅λ‹ˆλ‹€.

2. 예츑 λΆˆκ°€λŠ₯ν•œ UI 및 μƒνƒœ

비동기 μž‘μ—…μ΄ μ‹€νŒ¨ν–ˆλŠ”λ° μ—λŸ¬ μ²˜λ¦¬κ°€ μ—†λ‹€λ©΄, μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μƒνƒœ(state)λŠ” μ˜λ„ν•œ λŒ€λ‘œ μ—…λ°μ΄νŠΈλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, μ‚¬μš©μž 정보λ₯Ό λΆˆλŸ¬μ˜€λŠ” API 호좜이 μ‹€νŒ¨ν–ˆμ§€λ§Œ μ—λŸ¬λ₯Ό μž‘μ§€ μ•ŠμœΌλ©΄, isLoading μƒνƒœκ°€ false둜 λ°”λ€Œλ”λΌλ„ user λ°μ΄ν„°λŠ” μ—¬μ „νžˆ nullμ΄κ±°λ‚˜ 이전 κ°’ κ·ΈλŒ€λ‘œ λ‚¨μ•„μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€. 이둜 인해 μ‚¬μš©μžμ—κ²Œ 빈 ν™”λ©΄, 였래된 데이터, λ˜λŠ” κΉ¨μ§„ UIκ°€ ν‘œμ‹œλ  수 있으며, μ΄λŠ” μ‹¬κ°ν•œ μ‚¬μš©μž κ²½ν—˜ μ €ν•˜λ‘œ μ΄μ–΄μ§‘λ‹ˆλ‹€.

3. λ””λ²„κΉ…μ˜ 어렀움

μ—λŸ¬κ°€ λ°œμƒν•΄λ„ λͺ…ν™•ν•œ λ©”μ‹œμ§€λ‚˜ 처리 둜직이 μ—†μœΌλ©΄, 문제의 원인을 νŒŒμ•…ν•˜κΈ° 맀우 μ–΄λ ΅μŠ΅λ‹ˆλ‹€. μ–΄λŠ λΆ€λΆ„μ—μ„œ, μ™œ μ—λŸ¬κ°€ λ°œμƒν–ˆλŠ”μ§€ μΆ”μ ν•˜κΈ° μœ„ν•΄ 더 λ§Žμ€ μ‹œκ°„κ³Ό λ…Έλ ₯이 ν•„μš”ν•˜κ²Œ λ©λ‹ˆλ‹€.

4. μžμ› λˆ„μˆ˜ 및 μ˜€μž‘λ™ κ°€λŠ₯μ„±

일뢀 비동기 μž‘μ—…μ€ 성곡 μ‹œ νŠΉμ • μžμ›μ„ ν•΄μ œν•˜κ±°λ‚˜ λ‹€μŒ 단계λ₯Ό μ§„ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€. μ—λŸ¬ 처리 없이 μ‹€νŒ¨ν•˜λŠ” 경우, μ΄λŸ¬ν•œ μžμ› ν•΄μ œ 둜직이 μ‹€ν–‰λ˜μ§€ μ•Šμ•„ μžμ› λˆ„μˆ˜κ°€ λ°œμƒν•˜κ±°λ‚˜, μ˜ˆμƒμΉ˜ λͺ»ν•œ μƒν™©μ—μ„œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ˜€μž‘λ™ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

How to Fix?

μ–΄λ–»κ²Œ μˆ˜μ •ν•΄μ•Ό ν•˜λŠ”κ°€?

비동기 μž‘μ—…μ˜ μ—λŸ¬λ₯Ό 효과적으둜 μ²˜λ¦¬ν•˜λŠ” κ°€μž₯ 기본적인 방법은 Promise.prototype.catch() λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜κ±°λ‚˜ async/await ꡬ문 λ‚΄μ—μ„œ try...catch 블둝을 μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. μ–΄λ–€ 방법을 μ‚¬μš©ν•˜λ“ , μ—λŸ¬ λ°œμƒ μ‹œ μ‚¬μš©μžμ—κ²Œ ν”Όλ“œλ°±μ„ μ œκ³΅ν•˜κ³ , μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μƒνƒœλ₯Ό μ•ˆμ •μ μœΌλ‘œ κ΄€λ¦¬ν•˜λŠ” 것이 μ€‘μš”ν•©λ‹ˆλ‹€.

1. Promise.prototype.catch() μ‚¬μš©ν•˜κΈ°

λͺ¨λ“  Promise 체인의 λ§ˆμ§€λ§‰μ— .catch() λ©”μ„œλ“œλ₯Ό λΆ™μ—¬ μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•©λ‹ˆλ‹€. μ΄λŠ” Promise 기반 API (Fetch API λ“±)λ₯Ό 직접 μ‚¬μš©ν•  λ•Œ μœ μš©ν•©λ‹ˆλ‹€.

fetch('/api/data')
  .then(response => response.json())
  .then(data => {
    // μ„±κ³΅μ μœΌλ‘œ 데이터 처리
  })
  .catch(error => {
    // μ—λŸ¬ λ°œμƒ μ‹œ 처리
    console.error('데이터λ₯Ό κ°€μ Έμ˜€λŠ” 쀑 였λ₯˜ λ°œμƒ:', error);
    // μ‚¬μš©μžμ—κ²Œ 였λ₯˜ λ©”μ‹œμ§€ ν‘œμ‹œ λ“±
  });

2. async/await와 try...catch μ‚¬μš©ν•˜κΈ°

가독성 높은 비동기 μ½”λ“œλ₯Ό μœ„ν•΄ async/awaitλ₯Ό μ‚¬μš©ν•˜λŠ” 경우, 동기 μ½”λ“œμ²˜λŸΌ try...catch 블둝을 μ‚¬μš©ν•˜μ—¬ μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” 더 직관적이고 체계적인 μ—λŸ¬ 관리λ₯Ό κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.

async function fetchData() {
  try {
    const response = await fetch('/api/data');
    if (!response.ok) {
      // HTTP μ—λŸ¬ μƒνƒœ(4xx, 5xx)도 μ—λŸ¬λ‘œ κ°„μ£Όν•˜μ—¬ throw
      throw new Error(`HTTP μ—λŸ¬! μƒνƒœ: ${response.status}`);
    }
    const data = await response.json();
    // μ„±κ³΅μ μœΌλ‘œ 데이터 처리
  } catch (error) {
    // μ—λŸ¬ λ°œμƒ μ‹œ 처리
    console.error('데이터λ₯Ό κ°€μ Έμ˜€λŠ” 쀑 였λ₯˜ λ°œμƒ:', error);
    // μ‚¬μš©μžμ—κ²Œ 였λ₯˜ λ©”μ‹œμ§€ ν‘œμ‹œ λ“±
  }
}

3. μ—λŸ¬ μƒνƒœ 및 UI ν”Όλ“œλ°± 관리

μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ λ‹¨μˆœνžˆ μ½˜μ†”μ— κΈ°λ‘ν•˜λŠ” 것을 λ„˜μ–΄, μ‚¬μš©μžμ—κ²Œ μ‹œκ°μ μΈ ν”Όλ“œλ°±μ„ μ œκ³΅ν•΄μ•Ό ν•©λ‹ˆλ‹€. React μ»΄ν¬λ„ŒνŠΈμ—μ„œλŠ” useState 훅을 μ‚¬μš©ν•˜μ—¬ μ—λŸ¬ μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜κ³ , 이 μƒνƒœμ— 따라 UIλ₯Ό λ‹€λ₯΄κ²Œ λ Œλ”λ§ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό ν‘œμ‹œν•˜κ±°λ‚˜ μž¬μ‹œλ„ λ²„νŠΌμ„ μ œκ³΅ν•˜λŠ” λ“±μ˜ UX κ°œμ„ μ„ κ³ λ €ν•΄μ•Ό ν•©λ‹ˆλ‹€.

// μ˜ˆμ‹œ: React μ»΄ν¬λ„ŒνŠΈμ—μ„œ μ—λŸ¬ μƒνƒœ 관리
import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);
      setError(null); // μƒˆλ‘œμš΄ μš”μ²­ μ‹œμž‘ μ „ μ—λŸ¬ μ΄ˆκΈ°ν™”
      try {
        const response = await fetch('/api/data');
        if (!response.ok) {
          throw new Error('데이터 λ‘œλ“œ μ‹€νŒ¨!');
        }
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err);
        console.error('데이터 λ‘œλ”© 쀑 μ—λŸ¬:', err);
      } finally {
        setIsLoading(false);
      }
    };
    fetchData();
  }, []);

  if (isLoading) return <div>λ‘œλ”© 쀑...</div>;
  if (error) return <div style={{ color: 'red' }}>μ—λŸ¬ λ°œμƒ: {error.message}</div>;
  return <div>데이터: {JSON.stringify(data)}</div>;
}

4. μ „μ—­ μ—λŸ¬ 핸듀링 (선택 사항)

μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ „λ°˜μ— 걸쳐 μ²˜λ¦¬λ˜μ§€ μ•Šμ€ Promise μ—λŸ¬λ₯Ό ν¬μ°©ν•˜κΈ° μœ„ν•΄ window.addEventListener('unhandledrejection', ...)λ₯Ό μ‚¬μš©ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” μ΅œμ’… λ°©μ–΄μ„  역할을 ν•˜μ§€λ§Œ, κ°œλ³„ 비동기 μž‘μ—…μ—λŠ” μ—¬μ „νžˆ κ°œλ³„μ μΈ .catch()λ‚˜ try...catch 블둝을 μ μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

Before Code (Bad)

// React μ»΄ν¬λ„ŒνŠΈ μ˜ˆμ‹œ
import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    // μ—λŸ¬ 처리 없이 μ‚¬μš©μž 데이터λ₯Ό κ°€μ Έμ˜΄
    fetch(`/api/users/${userId}`)
      .then(response => response.json())
      .then(data => {
        setUser(data);
        setIsLoading(false);
      }); // 🚨 .catch() λˆ„λ½!

    // λ§Œμ•½ fetchκ°€ μ‹€νŒ¨ν•˜λ©΄? λ„€νŠΈμ›Œν¬ 문제, μ„œλ²„ μ—λŸ¬ λ“±
    // `user` μƒνƒœλŠ” null, `isLoading`은 falseκ°€ λ˜μ–΄
    // UIλŠ” 빈 ν™”λ©΄μ΄κ±°λ‚˜ λ‘œλ”© μŠ€ν”Όλ„ˆκ°€ 사라진 μ±„λ‘œ μ΄μƒν•œ μƒνƒœλ₯Ό λ³΄μ—¬μ€Œ.
    // μ½˜μ†”μ—λŠ” Unhandled Promise Rejection 경고만 λ‚¨μŒ.
  }, [userId]);

  if (isLoading) return <div>λ‘œλ”© 쀑...</div>;
  if (!user) return <div>μ‚¬μš©μž 정보λ₯Ό 뢈러올 수 μ—†μŠ΅λ‹ˆλ‹€.</div>; // 이 λ©”μ‹œμ§€μ‘°μ°¨ μ•ˆ λ‚˜μ˜¬ 수 있음

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

After Code (Good)

// React μ»΄ν¬λ„ŒνŠΈ μ˜ˆμ‹œ
import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null); // μ—λŸ¬ μƒνƒœ μΆ”κ°€

  useEffect(() => {
    const fetchUser = async () => {
      setIsLoading(true);
      setError(null); // μƒˆλ‘œμš΄ μš”μ²­ μ „ μ—λŸ¬ μ΄ˆκΈ°ν™”
      try {
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) {
          // HTTP μ—λŸ¬ μƒνƒœ (4xx, 5xx) 처리
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        const data = await response.json();
        setUser(data);
      } catch (err) {
        console.error("데이터 λ‘œλ”© 쀑 μ—λŸ¬ λ°œμƒ:", err);
        setError(err); // μ—λŸ¬ μƒνƒœ μ—…λ°μ΄νŠΈ
      } finally {
        setIsLoading(false); // λ‘œλ”© μƒνƒœλŠ” 항상 ν•΄μ œ
      }
    };

    fetchUser();
  }, [userId]);

  if (isLoading) return <div>λ‘œλ”© 쀑...</div>;
  if (error) return <div style={{ color: 'red' }}>데이터λ₯Ό λΆˆλŸ¬μ˜€λŠ” 데 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€: {error.message}</div>;
  if (!user) return <div>μ‚¬μš©μž 정보λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.</div>; // μ—λŸ¬κ°€ μ•„λ‹Œ 경우의 μ‚¬μš©μž μ—†μŒ 처리

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}