refactor(api): implement abort signal handling in API requests across multiple components to improve cancellation of ongoing requests and prevent memory leaks
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m12s

This commit is contained in:
2026-02-22 23:53:15 +07:00
parent 3fda43cf2c
commit a14f10d048
17 changed files with 278 additions and 128 deletions
+20 -10
View File
@@ -113,9 +113,12 @@ function BillingManager() {
});
useEffect(() => {
fetchBillingData();
fetchExchangeRates();
fetchServers();
const controller = new AbortController();
const signal = controller.signal;
fetchBillingData(signal);
fetchExchangeRates(signal);
fetchServers(signal);
return () => controller.abort();
}, []);
// Автоматическая синхронизация: добавляем серверы без записей в биллинге
@@ -162,11 +165,12 @@ function BillingManager() {
}, [servers, loading]);
const fetchExchangeRates = async () => {
const fetchExchangeRates = async (abortSignal) => {
setRatesLoading(true);
const opts = abortSignal ? { signal: abortSignal } : {};
try {
// Основной источник: ЦБ РФ (base = RUB)
const cbr = await axios.get('https://www.cbr-xml-daily.ru/latest.js');
const cbr = await axios.get('https://www.cbr-xml-daily.ru/latest.js', opts);
const rubPerUsd = cbr.data && cbr.data.rates?.USD ? 1 / Number(cbr.data.rates.USD) : null;
const rubPerEur = cbr.data && cbr.data.rates?.EUR ? 1 / Number(cbr.data.rates.EUR) : null;
@@ -180,12 +184,13 @@ function BillingManager() {
return;
}
} catch (error) {
if (error?.name === 'CanceledError' || error?.name === 'AbortError' || error?.code === 'ERR_CANCELED') return;
console.warn('ЦБ недоступен, пробуем резервный источник:', error?.message);
}
try {
// Резервный источник: open.er-api
const response = await axios.get('https://open.er-api.com/v6/latest/USD');
const response = await axios.get('https://open.er-api.com/v6/latest/USD', opts);
const rates = response.data?.rates || {};
setExchangeRates({
USD: 1,
@@ -193,6 +198,7 @@ function BillingManager() {
RUB: rates.RUB || 1
});
} catch (error) {
if (error?.name === 'CanceledError' || error?.name === 'AbortError' || error?.code === 'ERR_CANCELED') return;
console.error('Ошибка при загрузке курсов валют:', error);
// Фолбек на разумные значения
setExchangeRates({ USD: 1, EUR: 1.05, RUB: 95 });
@@ -201,19 +207,22 @@ function BillingManager() {
}
};
const fetchServers = async () => {
const fetchServers = async (abortSignal) => {
const opts = abortSignal ? { signal: abortSignal } : {};
try {
const res = await api.get(`/servers`);
const res = await api.get(`/servers`, opts);
setServers(Array.isArray(res.data) ? res.data : []);
} catch (error) {
if (error?.name === 'CanceledError' || error?.name === 'AbortError' || error?.code === 'ERR_CANCELED') return;
console.error('Ошибка при загрузке серверов:', error);
}
};
const fetchBillingData = async () => {
const fetchBillingData = async (abortSignal) => {
setLoading(true);
const opts = abortSignal ? { signal: abortSignal } : {};
try {
const response = await api.get(`/billing`);
const response = await api.get(`/billing`, opts);
const rates = exchangeRates; // из closure, на первой загрузке может быть {1,1,1} — для USD достаточно
const toRUB = (amt, ccy) => {
if (!amt) return 0;
@@ -251,6 +260,7 @@ function BillingManager() {
setBillingData(normalized);
setError('');
} catch (err) {
if (err?.name === 'CanceledError' || err?.name === 'AbortError' || err?.code === 'ERR_CANCELED') return;
console.error('Ошибка при загрузке данных биллинга:', err);
setError('Ошибка при загрузке данных');
setBillingData([]);