실전 비동기 프로그래밍 패턴

2025. 3. 30. 22:45Web Development/JavaScript

실전 비동기 프로그래밍 패턴

타임아웃 처리

Promise가 지정된 시간 내에 완료되지 않으면 거부되도록 타임아웃을 설정할 수 있습니다.

function fetchWithTimeout(url, options = {}, timeout = 5000) {
  return new Promise((resolve, reject) => {
    // 주 Promise
    const fetchPromise = fetch(url, options)
      .then(resolve)
      .catch(reject);
    
    // 타임아웃 Promise
    const timeoutPromise = new Promise((_, reject) => {
      setTimeout(() => {
        reject(new Error(`요청이 ${timeout}ms 후에 시간 초과되었습니다.`));
      }, timeout);
    });
    
    // 둘 중 먼저 완료되는 것 선택
    Promise.race([fetchPromise, timeoutPromise]);
  });
}

// 사용 예시
async function fetchData() {
  try {
    const response = await fetchWithTimeout('https://api.example.com/data', {}, 3000);
    const data = await response.json();
    console.log('데이터:', data);
  } catch (error) {
    console.error('오류:', error.message);
  }
}

재시도 메커니즘

일시적인 오류가 발생할 경우 몇 번의 재시도를 수행하는 패턴입니다.

async function fetchWithRetry(url, options = {}, maxRetries = 3, retryDelay = 1000) {
  let lastError;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      if (!response.ok) {
        throw new Error(`HTTP 오류! 상태: ${response.status}`);
      }
      
      return response; // 성공 시 즉시 반환
    } catch (error) {
      console.warn(`시도 ${attempt + 1}/${maxRetries} 실패:`, error.message);
      lastError = error;
      
      if (attempt < maxRetries - 1) {
        // 다음 시도 전에 지연
        await new Promise(resolve => setTimeout(resolve, retryDelay));
        // 지수 백오프 (선택 사항)
        retryDelay *= 2;
      }
    }
  }
  
  throw new Error(`${maxRetries}번 시도 후 실패: ${lastError.message}`);
}

// 사용 예시
async function getData() {
  try {
    const response = await fetchWithRetry('https://api.example.com/data', {}, 3, 1000);
    const data = await response.json();
    console.log('성공적으로 데이터를 가져왔습니다:', data);
  } catch (error) {
    console.error('모든 재시도 실패:', error.message);
  }
}

비동기 반복 및 제어

배열의 요소를 비동기적으로 처리하는 다양한 방법입니다.

// 배열 요소 순차 처리
async function processSequentially(items) {
  const results = [];
  
  for (const item of items) {
    // 각 항목을 순차적으로 처리
    const result = await processItem(item);
    results.push(result);
  }
  
  return results;
}

// 배열 요소 병렬 처리
async function processInParallel(items) {
  // 모든 프로미스를 동시에 시작하고 결과 기다림
  const results = await Promise.all(items.map(item => processItem(item)));
  return results;
}

// 병렬 처리 (동시 실행 수 제한)
async function processWithConcurrencyLimit(items, concurrency = 3) {
  const results = [];
  const inProgress = new Set();
  
  for (const item of items) {
    // 처리를 시작하고 프로미스를 추적
    const promise = (async () => {
      const result = await processItem(item);
      // 완료 시 진행 중인 작업에서 제거
      inProgress.delete(promise);
      return result;
    })();
    
    inProgress.add(promise);
    results.push(promise);
    
    // 동시 실행 수가 제한에 도달하면 기다림
    if (inProgress.size >= concurrency) {
      await Promise.race(inProgress);
    }
  }
  
  // 모든 결과 반환
  return Promise.all(results);
}

캐싱 및 메모이제이션

동일한 비동기 작업 결과를 캐싱하여 성능을 향상시킬 수 있습니다.

// 간단한 비동기 메모이제이션 함수
function memoizeAsync(fn) {
  const cache = new Map();
  
  return async function(...args) {
    const key = JSON.stringify(args);
    
    if (cache.has(key)) {
      console.log('캐시에서 결과 반환:', key);
      return cache.get(key);
    }
    
    console.log('함수 실행 및 결과 캐싱:', key);
    const result = await fn(...args);
    cache.set(key, result);
    return result;
  };
}

// 사용 예시
const fetchUserDataCached = memoizeAsync(async (userId) => {
  // 실제로는 API 호출 등 비용이 큰 작업
  const response = await fetch(`https://api.example.com/users/${userId}`);
  return response.json();
});

// 첫 번째 호출 - API 호출
await fetchUserDataCached(1);
// 두 번째 호출 - 캐시에서 반환
await fetchUserDataCached(1);
// 다른 ID로 호출 - 새 API 호출
await fetchUserDataCached(2);

에러 처리 및 복구

강건한 비동기 코드를 위한 에러 처리 및 복구 패턴입니다.

// 백오프 재시도 + 폴백(fallback) 패턴
async function fetchWithRetryAndFallback(url, options = {}, maxRetries = 3) {
  let retryCount = 0;
  let lastError;
  
  // 재시도 로직
  while (retryCount < maxRetries) {
    try {
      const response = await fetch(url, options);
      if (!response.ok) {
        throw new Error(`HTTP 오류: ${response.status}`);
      }
      return await response.json();
    } catch (error) {
      lastError = error;
      console.warn(`시도 ${retryCount + 1}/${maxRetries} 실패:`, error.message);
      retryCount++;
      
      if (retryCount < maxRetries) {
        // 지수 백오프
        const delay = Math.pow(2, retryCount) * 1000 + Math.random() * 1000;
        console.log(`${delay}ms 후 재시도...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  // 모든 시도 실패 시 폴백 동작
  console.error("모든 재시도 실패. 폴백 데이터 사용:", lastError);
  return getFallbackData(url); // 캐시된 데이터 또는 기본값 반환
}

function getFallbackData(url) {
  // URL에 따라 적절한 폴백 데이터 제공
  if (url.includes('/users')) {
    return { error: true, fallback: true, users: [] };
  }
  if (url.includes('/products')) {
    return { error: true, fallback: true, products: [] };
  }
  return { error: true, fallback: true, data: null };
}

'Web Development > JavaScript' 카테고리의 다른 글

최신 JavaScript 문법  (0) 2025.03.30
모듈 시스템  (0) 2025.03.30
async/await  (0) 2025.03.30
Promise  (0) 2025.03.30
콜백 함수  (0) 2025.03.30