← Назад к вопросам

Где применяется Graceful Shutdown?

2.0 Middle🔥 181 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Graceful Shutdown в приложениях

Graceful Shutdown (мягкое выключение) - это контролируемый процесс завершения работы приложения, при котором:

  • Завершаются все активные операции
  • Закрываются соединения
  • Сохраняются данные
  • Освобождаются ресурсы

Для фронтенд-разработчика это особенно важно.

Где применяется Graceful Shutdown

1. Single Page Applications (SPA)

Когда пользователь уходит со страницы или закрывает браузер:

// Без graceful shutdown - потеряются данные
window.addEventListener('beforeunload', (event) => {
  // Graceful shutdown
  // Сохраняем данные
  saveUserData();
  // Закрываем соединения
  closeWebSocketConnections();
  // Отправляем последние аналитику
  trackEvent('user_left_app');
});

// Если этого не сделать:
// - Незаканченные запросы будут отменены
// - Данные в формах потеряются
// - Соединения WebSocket останутся открытыми

2. React/Vue/Angular компоненты

Когда компонент размонтируется, нужно очистить ресурсы:

// React пример - graceful cleanup
function ChatComponent() {
  const [messages, setMessages] = React.useState([]);
  
  React.useEffect(() => {
    // Подключаемся к WebSocket
    const ws = new WebSocket('ws://server.com');
    
    ws.onmessage = (event) => {
      setMessages(prev => [...prev, event.data]);
    };
    
    // Graceful shutdown - cleanup функция
    return () => {
      console.log('Компонент размонтируется - мягко закрываем соединение');
      // Отправляем последнее сообщение
      ws.send(JSON.stringify({ type: 'user_left' }));
      // Закрываем соединение
      ws.close();
    };
  }, []);
  
  return <div>{messages.map(msg => <p>{msg}</p>)}</div>;
}

3. WebSocket соединения

При разрыве соединения или переключении между вкладками:

class WebSocketManager {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.messageQueue = []; // Очередь сообщений при разрыве
  }
  
  connect() {
    this.ws = new WebSocket(this.url);
    
    this.ws.onclose = (event) => {
      console.log('Соединение закрыто, выполняю graceful shutdown');
      this.gracefulShutdown();
    };
  }
  
  gracefulShutdown() {
    // Отправляем очередь накопленных сообщений при переподключении
    if (this.messageQueue.length > 0) {
      console.log(`Выполняю graceful shutdown: отправляю ${this.messageQueue.length} сообщений`);
      // Переподключаемся и отправляем
      this.reconnectAndFlush();
    }
    // Очищаем слушатели
    this.removeAllListeners();
  }
  
  reconnectAndFlush() {
    this.connect();
    this.ws.onopen = () => {
      this.messageQueue.forEach(msg => this.ws.send(msg));
      this.messageQueue = [];
    };
  }
}

4. Очистка таймеров и интервалов

function SearchComponent() {
  const [results, setResults] = React.useState([]);
  const debounceTimerRef = React.useRef(null);
  
  React.useEffect(() => {
    const handleSearch = (query) => {
      // Graceful cleanup: очищаем предыдущий таймер
      if (debounceTimerRef.current) {
        clearTimeout(debounceTimerRef.current);
      }
      
      // Устанавливаем новый
      debounceTimerRef.current = setTimeout(() => {
        fetchResults(query);
      }, 300);
    };
    
    // Graceful shutdown при размонтировании
    return () => {
      if (debounceTimerRef.current) {
        clearTimeout(debounceTimerRef.current);
      }
    };
  }, []);
  
  return <input onChange={(e) => handleSearch(e.target.value)} />;
}

5. EventListeners

function FullscreenComponent() {
  React.useEffect(() => {
    const handleResize = () => {
      console.log('Размер экрана изменился');
    };
    
    const handleKeyPress = (event) => {
      if (event.key === 'Escape') {
        closeFullscreen();
      }
    };
    
    // Добавляем слушатели
    window.addEventListener('resize', handleResize);
    document.addEventListener('keydown', handleKeyPress);
    
    // Graceful shutdown - удаляем слушатели
    return () => {
      window.removeEventListener('resize', handleResize);
      document.removeEventListener('keydown', handleKeyPress);
      console.log('Слушатели удалены - graceful shutdown');
    };
  }, []);
  
  return <div>Fullscreen content</div>;
}

6. HTTP запросы

function DataFetcher() {
  const [data, setData] = React.useState(null);
  const abortControllerRef = React.useRef(null);
  
  React.useEffect(() => {
    // Создаём AbortController для возможности отмены
    abortControllerRef.current = new AbortController();
    
    fetch('/api/data', {
      signal: abortControllerRef.current.signal
    })
      .then(res => res.json())
      .then(data => setData(data))
      .catch(err => {
        if (err.name !== 'AbortError') {
          console.error('Ошибка при загрузке', err);
        }
      });
    
    // Graceful shutdown - отмена запроса если компонент размонтирован
    return () => {
      console.log('Отменяю запрос - graceful shutdown');
      abortControllerRef.current.abort();
    };
  }, []);
  
  return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}

7. Service Workers (PWA)

// Service Worker graceful shutdown
self.addEventListener('install', (event) => {
  console.log('Service Worker установлена');
  event.waitUntil(caches.open('v1').then(cache => {
    return cache.addAll(['/index.html', '/styles.css']);
  }));
});

self.addEventListener('activate', (event) => {
  console.log('Service Worker активирована');
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== 'v1') {
            // Graceful shutdown - удаляем старые кэши
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    // Graceful shutdown - переходим на новую версию
    self.skipWaiting();
  }
});

8. Modal диалоги и overlay

function Modal({ isOpen, onClose }) {
  const modalRef = React.useRef(null);
  
  React.useEffect(() => {
    if (isOpen) {
      const handleOutsideClick = (e) => {
        if (modalRef.current && !modalRef.current.contains(e.target)) {
          onClose();
        }
      };
      
      document.addEventListener('click', handleOutsideClick);
      
      // Graceful shutdown - удаляем слушатель
      return () => {
        document.removeEventListener('click', handleOutsideClick);
      };
    }
  }, [isOpen, onClose]);
  
  return (
    isOpen && <div ref={modalRef} className="modal">{/* ... */}</div>
  );
}

Правила Graceful Shutdown

// ✅ Правильно
function GoodComponent() {
  React.useEffect(() => {
    const handler = () => { /* ... */ };
    window.addEventListener('event', handler);
    
    // Всегда очищаем!
    return () => {
      window.removeEventListener('event', handler);
    };
  }, []);
}

// ❌ Неправильно
function BadComponent() {
  React.useEffect(() => {
    const handler = () => { /* ... */ };
    window.addEventListener('event', handler);
    
    // Забыли удалить слушатель - memory leak!
  }, []);
}

Почему это важно

  • Предотвращение утечек памяти - неудалённые слушатели занимают память
  • Предотвращение потери данных - сохраняем состояние перед выходом
  • Корректное завершение операций - не прерываем посередине
  • Улучшение UX - плавное переключение между экранами
  • Сокращение нагрузки на сервер - корректное закрытие соединений

Заключение

Graceful Shutdown в фронтенде - это гарантия чистого завершения операций без утечек ресурсов. Это основа для написания стабильных, долгоживущих приложений, которые работают месяцы без перезагрузок.

Где применяется Graceful Shutdown? | PrepBro