Пример 203: Ресурс с настройками "из коробки" (2)
Ожидается следующее:
- Всего сетевых вызовов: 4 запроса (1 стартовый + 3 повторных)
- Максимальное время на один запрос: Строго 2.5 секунды
- Общее время удержания лоадера на экране: Около 17 секунд (4 запроса по 2.5 секунды + ~7 секунд суммарного сна в паузах между ними)
tsx
import baseClasses from '~/ui.common.module.scss'
import btnClasses from '~/ui.button.module.scss'
import { AbstractService, ReactiveEngine, useReactiveValue } from '@pravosleva/reactive-engine'
import clsx from 'clsx'
const BASE_API_URL = import.meta.env.VITE_BASE_API_URL
class Logic extends AbstractService {
public counter = this.engine.signal<number>(0, 'example-203:signal:counter');
public apiState = this.engine.resource(
async (counterValue, abortSignal) => {
// Имитируем запрос к бэкенду, который искусственно зависает на 15 секунд
const res = await fetch(
[
`${BASE_API_URL}/profile/search`,
'?',
[
`counter=${counterValue}`,
'_responseDelay=15000',
].join('&')
].join(''),
{ signal: abortSignal }
)
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`)
return res.json()
},
this.counter,
{
name: 'example-203:resource:timeout-and-retry',
timeout: 2500, // Прервать запрос, если сервер не отвечает более 2.5 секунд
retryCount: 3, // Сделать до 3 повторных попыток после каждого таймаута
retryDelay: 1000, // Базовая задержка между попытками
isExponentialBackoffEnabled: true, // Прогрессивно увеличивать паузу (~1с -> ~2с -> ~4с)
maxRetryDelay: 5000, // Ограничить максимальное время ожидания 5 секундами
validateBeforeFetch: (counterValue) => {
if (counterValue === 0) {
return `Not started (pre-validation before fetch) for count value ${counterValue}`;
}
return true; // Валидация успешна, можно делать fetch
},
}
)
public inc() {
this.counter.value += 1
}
}
const engine = new ReactiveEngine()
export const Example203 = () => {
const logic = engine.inject(Logic)
const counter = engine.use(logic.counter)
// Извлекаем из хука данные, ошибку и наш новый реактивный флаг повторных попыток
const { loading, data, error, isRetrying } = useReactiveValue(logic.apiState)
return (
<div className={clsx(baseClasses.unit, baseClasses.stack2)}>
<div className={baseClasses.absoluteUnitLabel} title='Resource with timeout support'>
example 23 | Resource with timeout support
</div>
<code>{BASE_API_URL}</code>
<div style={{ minWidth: '100%' }}>
<button
onClick={() => logic.inc()}
className={clsx(btnClasses.btn, btnClasses.neonBtn, btnClasses['neonBtn--primary'], btnClasses['neonBtn--outlined'])}
>
({counter}) Test Request Timeout
</button>
</div>
<div className={baseClasses.unitInternalWrapper}>
<div style={{ display: 'flex', gap: '8px', flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center' }}>
{/* Интеллектуальный вывод статуса: если идет повтор, подсвечиваем его отдельно */}
<span>
{isRetrying ? '🟠 retrying (timeout)...' :
loading ? '🟡 loading...' :
!!data ? '🟢 ok' :
!!error ? '🔴 err' : '⚪'}
</span>
{/* Текст ошибки выводим только тогда, когда движок исчерпал все попытки и окончательно сдался */}
{!isRetrying && !!error?.message && <em>{error?.message}</em>}
</div>
</div>
<pre className={baseClasses.preNormalizedMin}>{JSON.stringify({ data }, null, 2)}</pre>
</div>
)
}