200 lines
4.5 KiB
TypeScript
200 lines
4.5 KiB
TypeScript
import { ref, onMounted, onUnmounted } from 'vue'
|
|
|
|
export interface PollingOptions {
|
|
interval?: number // milliseconds
|
|
immediate?: boolean // whether to execute immediately on start
|
|
maxRetries?: number // maximum number of retries on error
|
|
retryDelay?: number // delay between retries in milliseconds
|
|
onError?: (error: Error) => void // error handler
|
|
}
|
|
|
|
export interface PollingState {
|
|
isPolling: boolean
|
|
isFetching: boolean
|
|
error: string | null
|
|
retryCount: number
|
|
lastFetchTime: Date | null
|
|
}
|
|
|
|
/**
|
|
* Composable for implementing polling/real-time updates
|
|
*
|
|
* @param callback - Function to execute on each poll
|
|
* @param options - Polling configuration options
|
|
* @returns Polling controls and state
|
|
*/
|
|
export const usePolling = <T>(
|
|
callback: () => Promise<T> | T,
|
|
options: PollingOptions = {}
|
|
) => {
|
|
const {
|
|
interval = 3000, // 3 seconds default
|
|
immediate = true,
|
|
maxRetries = 3,
|
|
retryDelay = 1000,
|
|
onError
|
|
} = options
|
|
|
|
// State
|
|
const state = ref<PollingState>({
|
|
isPolling: false,
|
|
isFetching: false,
|
|
error: null,
|
|
retryCount: 0,
|
|
lastFetchTime: null
|
|
})
|
|
|
|
// Polling interval reference
|
|
let pollInterval: NodeJS.Timeout | null = null
|
|
let retryTimeout: NodeJS.Timeout | null = null
|
|
|
|
// Execute the polling callback
|
|
const executePoll = async (): Promise<T | null> => {
|
|
if (state.value.isFetching) {
|
|
return null // Skip if already fetching
|
|
}
|
|
|
|
state.value.isFetching = true
|
|
state.value.error = null
|
|
|
|
try {
|
|
const result = await callback()
|
|
state.value.lastFetchTime = new Date()
|
|
state.value.retryCount = 0 // Reset retry count on success
|
|
return result
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
state.value.error = errorMessage
|
|
state.value.retryCount++
|
|
|
|
// Call error handler if provided
|
|
if (onError) {
|
|
onError(error instanceof Error ? error : new Error(errorMessage))
|
|
}
|
|
|
|
// Handle retries
|
|
if (state.value.retryCount <= maxRetries) {
|
|
console.warn(`Polling error (retry ${state.value.retryCount}/${maxRetries}):`, errorMessage)
|
|
|
|
// Schedule retry
|
|
if (retryTimeout) {
|
|
clearTimeout(retryTimeout)
|
|
}
|
|
|
|
retryTimeout = setTimeout(() => {
|
|
executePoll()
|
|
}, retryDelay)
|
|
} else {
|
|
console.error(`Polling failed after ${maxRetries} retries:`, errorMessage)
|
|
stopPolling() // Stop polling after max retries
|
|
}
|
|
|
|
return null
|
|
} finally {
|
|
state.value.isFetching = false
|
|
}
|
|
}
|
|
|
|
// Start polling
|
|
const startPolling = () => {
|
|
if (state.value.isPolling) {
|
|
return // Already polling
|
|
}
|
|
|
|
state.value.isPolling = true
|
|
state.value.error = null
|
|
|
|
// Execute immediately if requested
|
|
if (immediate) {
|
|
executePoll()
|
|
}
|
|
|
|
// Set up interval
|
|
pollInterval = setInterval(() => {
|
|
executePoll()
|
|
}, interval)
|
|
}
|
|
|
|
// Stop polling
|
|
const stopPolling = () => {
|
|
if (pollInterval) {
|
|
clearInterval(pollInterval)
|
|
pollInterval = null
|
|
}
|
|
|
|
if (retryTimeout) {
|
|
clearTimeout(retryTimeout)
|
|
retryTimeout = null
|
|
}
|
|
|
|
state.value.isPolling = false
|
|
state.value.isFetching = false
|
|
}
|
|
|
|
// Toggle polling
|
|
const togglePolling = () => {
|
|
if (state.value.isPolling) {
|
|
stopPolling()
|
|
} else {
|
|
startPolling()
|
|
}
|
|
}
|
|
|
|
// Force immediate execution
|
|
const forcePoll = async (): Promise<T | null> => {
|
|
return await executePoll()
|
|
}
|
|
|
|
// Update polling interval
|
|
const updateInterval = (newInterval: number) => {
|
|
const wasPolling = state.value.isPolling
|
|
|
|
if (wasPolling) {
|
|
stopPolling()
|
|
}
|
|
|
|
// Update interval in options (for next start)
|
|
options.interval = newInterval
|
|
|
|
if (wasPolling) {
|
|
startPolling()
|
|
}
|
|
}
|
|
|
|
// Cleanup on unmount
|
|
onUnmounted(() => {
|
|
stopPolling()
|
|
})
|
|
|
|
// Auto-start on mount if immediate is true
|
|
onMounted(() => {
|
|
if (immediate) {
|
|
startPolling()
|
|
}
|
|
})
|
|
|
|
return {
|
|
// State
|
|
state: state.value,
|
|
isPolling: state.value.isPolling,
|
|
isFetching: state.value.isFetching,
|
|
error: state.value.error,
|
|
retryCount: state.value.retryCount,
|
|
lastFetchTime: state.value.lastFetchTime,
|
|
|
|
// Controls
|
|
startPolling,
|
|
stopPolling,
|
|
togglePolling,
|
|
forcePoll,
|
|
updateInterval,
|
|
|
|
// Helper
|
|
resetError: () => {
|
|
state.value.error = null
|
|
state.value.retryCount = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
export default usePolling |