Polling Manager Examples
This page shows practical usage patterns for Polling Manager in two contexts:
- Plain JavaScript/TypeScript (domain adapter or service-style orchestration)
- React components (pages/modules)
Plain JavaScript Example
Use this pattern in non-React orchestration layers where you want a reusable subscription API.
import { pollingManager } from '@/lib/internal/PollingManager';
type EncodingProgress = {
status: 'PROCESSING' | 'READY' | 'FAILED';
percent: number;
};
async function fetchEncodingProgress(contentId: string): Promise<EncodingProgress> {
// Example placeholder: call your transport layer here.
return {
status: 'PROCESSING',
percent: 42,
};
}
export function startEncodingProgressWatch(contentId: string, onProgress: (percent: number) => void) {
return pollingManager.subscribe({
key: `encoding:progress:${contentId}`,
intervalMs: 5000,
pollFn: () => fetchEncodingProgress(contentId),
onResult: (result) => onProgress(result.percent),
onError: (error) => {
// Route to your own logging/monitoring path.
console.error('Encoding poll failed', error);
},
shouldStop: (result) => result.status === 'READY' || result.status === 'FAILED',
});
}
// Consumer:
const subscription = startEncodingProgressWatch('vod-123', (percent) => {
console.log('Encoding progress:', percent);
});
// Later cleanup:
subscription.unsubscribe();
Notes
- Call
unsubscribe()from the owner that started the watch. - Use stable keys to get dedup across multiple consumers watching the same entity.
- Keep domain terminal logic inside
shouldStop. - For existing keys, task-defining options must match (
pollFn,intervalMs,shouldStop,pauseWhenHidden) or subscribe fails fast.
React Example (Pages/Modules)
Recommended pattern without introducing a custom hook.
'use client';
import { useEffect, useRef, useState } from 'react';
import { pollingManager } from '@/lib/internal/PollingManager';
type EncodingProgress = {
status: 'PROCESSING' | 'READY' | 'FAILED';
percent: number;
};
async function fetchEncodingProgress(contentId: string): Promise<EncodingProgress> {
return {
status: 'PROCESSING',
percent: 42,
};
}
export function EncodingProgressTile({ contentId, enabled = true }: { contentId: string; enabled?: boolean }) {
const [progress, setProgress] = useState<number>(0);
const [error, setError] = useState<string | null>(null);
const subscriptionRef = useRef<ReturnType<typeof pollingManager.subscribe<EncodingProgress>> | null>(null);
useEffect(() => {
if (!enabled || !contentId) return;
subscriptionRef.current = pollingManager.subscribe<EncodingProgress>({
key: `encoding:progress:${contentId}`,
intervalMs: 5000,
pollFn: () => fetchEncodingProgress(contentId),
onResult: (result) => {
setProgress(result.percent);
},
onError: () => {
setError('Unable to refresh encoding progress');
},
shouldStop: (result) => result.status === 'READY' || result.status === 'FAILED',
});
return () => {
subscriptionRef.current?.unsubscribe();
subscriptionRef.current = null;
};
}, [contentId, enabled]);
// Keep callbacks fresh without unsubscribe/resubscribe churn.
useEffect(() => {
subscriptionRef.current?.update({
onResult: (result) => {
setProgress(result.percent);
},
onError: () => {
setError('Unable to refresh encoding progress');
},
});
}, []);
if (!enabled) return null;
return (
<div>
<p>Encoding progress: {progress}%</p>
{error ? <p>{error}</p> : null}
</div>
);
}
Shared-key dedup scenario
// List card and detail panel both subscribe to:
// key = "encoding:progress:vod-123"
//
// Result:
// - one task in PollingManager
// - one timer
// - two subscribers
// - no second immediate poll on the second subscriber
Anti-patterns
- Creating raw
setIntervalloops inside each component for the same resource - Generating unique keys per component instance for the same entity
- Unsubscribing/re-subscribing every rerender when callback update is enough
- Mutating task-defining fields through
update(not supported)