Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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 в фронтенде - это гарантия чистого завершения операций без утечек ресурсов. Это основа для написания стабильных, долгоживущих приложений, которые работают месяцы без перезагрузок.