-
Notifications
You must be signed in to change notification settings - Fork 0
Stage 6: Polling Service Implementation
mandla-enkosi edited this page Apr 5, 2025
·
1 revision
-
Objective:
- Implement the
startPollingfunction. - Support:
- Configurable intervals
- Standard backoff strategies (exponential, linear, Fibonacci, fixed)
- Network awareness (
pauseWhenOffline) - Termination conditions
- Cancellation via handle
- Implement the
-
Expected Result:
- A reliable
startPollingfunction that:- Uses the configured
httpClient - Handles polling lifecycle and errors gracefully
- Allows external control via the returned handle
- Uses the configured
- A reliable
-
Pre-Stage Requirements:
- HTTP Client.
- Platform Abstraction Layer providing
INetworkInfo. - Internal State Management providing store setters.
-
External Dependencies: Internal
httpClient,INetworkInfo. - Environment Configurations: Default polling parameters (interval, backoff).
-
Visual Aids:
Polling Service Lifecycle
LoadingsequenceDiagram participant App as Application participant PollingService as startPolling Func / Manager participant Timers as (setTimeout/clearTimeout) participant HTTPClient as IHttpClient participant Backoff as IBackoffStrategy participant Network as INetworkInfo participant StateStore as InternalStateStore participant Handle as PollingHandle App->>PollingService: startPolling(options) PollingService->>PollingService: Generate unique poll ID PollingService->>StateStore: Add ID to operations.activePolls PollingService->>PollingService: Create PollingHandle (with ID, stop method) PollingService-->>App: Return PollingHandle PollingService->>Timers: setTimeout(runPoll, options.initialDelay || options.interval) loop Poll Loop Note over Timers, PollingService: Timer fires... Timers-->>PollingService: runPoll() PollingService->>Network: getCurrentState() alt Network Online OR pauseWhenOffline=false Network-->>PollingService: Online State PollingService->>HTTPClient: request({ url, method, ..., signal: handle.abortController.signal }) alt HTTP Request Success (2xx) HTTPClient-->>PollingService: Success Response (data, status) PollingService->>Backoff: reset() // Reset error backoff on success PollingService->>PollingService: Check terminateWhen(data, status) alt Condition Met OR terminateOnSuccessStatus=true PollingService->>App: onSuccess Callback (final) PollingService->>App: onTerminate Callback (CONDITION_MET) PollingService->>PollingService: cleanupPoll(handle) PollingService->>StateStore: Remove ID from operations.activePolls Note over PollingService: Loop End else Condition Not Met PollingService->>App: onSuccess Callback (intermediate) PollingService->>Timers: setTimeout(runPoll, options.interval) end else HTTP Request Failed (Error) HTTPClient-->>PollingService: Error (StandardError) PollingService->>App: onError Callback (error, attempt) alt Retryable Error AND attempts < maxAttempts AND time < pollingTimeout PollingService->>Backoff: calculateDelay(attempt) PollingService->>Timers: setTimeout(runPoll, calculatedDelay) else Non-Retryable OR Limits Reached PollingService->>App: onTerminate Callback (ERROR_LIMIT_REACHED / POLLING_TIMEOUT / etc., error) PollingService->>PollingService: cleanupPoll(handle) PollingService->>StateStore: Remove ID from operations.activePolls Note over PollingService: Loop End end end else Network Offline AND pauseWhenOffline=true Network-->>PollingService: Offline State PollingService->>Network: subscribe(networkChangeListener) // If not already subscribed Note over PollingService: Paused, waits for network online event... end end Note over App, PollingService: Later... App->>Handle: stop() Handle->>PollingService: signalStop(handle.id) PollingService->>PollingService: Abort ongoing HTTP request (handle.abortController.abort()) PollingService->>Timers: clearTimeout(scheduledPollTimer) PollingService->>App: onTerminate Callback (CANCELLED / MANUAL_STOP) PollingService->>PollingService: cleanupPoll(handle) PollingService->>StateStore: Remove ID from operations.activePolls
-
Modules/Files:
-
/packages/core/src/polling/index.ts: ExportstartPolling. -
/packages/core/src/polling/pollingService.ts:- Implement
startPollinglogic - Manage internal poll states (timers, attempts, AbortControllers mapped by ID)
- Handle scheduling
- Interact with
httpClient, backoff strategies, termination conditions, network info
- Implement
-
/packages/core/src/polling/backoffStrategies.ts: ImplementIBackoffStrategyfor exponential, linear, fibonacci, fixed. Include jitter logic. -
/packages/core/src/polling/terminationConditions.ts: Implement helper functions for checkingmaxAttempts,pollingTimeout, evaluatingterminateWhen. -
/packages/core/src/polling/types.ts: DefinePollingOptions,PollingHandle,TerminationReason,BackoffOptions,BackoffStrategyName. -
/packages/core/src/http/index.ts: Consumed to gethttpClient. -
/packages/core/src/platform/index.ts: Consumed to getINetworkInfo. -
/packages/core/src/state/store.ts: Updated withoperations.activePollsadd/remove.
-
-
Functionalities:
-
startPollingsetup and handle creation. - Polling loop management using
setTimeout. - HTTP request execution via
httpClient, passingAbortSignal. - Response handling: check termination, invoke callbacks, schedule next poll.
- Error handling: invoke callback, calculate backoff, schedule retry or terminate.
- Network awareness: Check
INetworkInfo, pause/resume based on status andpauseWhenOffline. - Cancellation: Implement
PollingHandle.stop()to clear timers and abort requests. - Resource cleanup on termination/stop.
- Update internal state tracking active polls.
-
// --- Polling Types (/packages/core/src/polling/types.ts) ---
import { StandardError, HttpClientRequestConfig } from '../http/types';
/** Defines the available backoff strategy names. */
export type BackoffStrategyName = 'exponential' | 'linear' | 'fibonacci' | 'fixed';
/** Options for configuring backoff behavior on errors. */
export interface BackoffOptions {
/** The strategy algorithm to use. */
strategy: BackoffStrategyName;
/**
* The base delay (in ms) for the first retry. If not provided,
* the poll's `interval` might be used as the base.
*/
baseDelay?: number;
/** Maximum delay (in ms) between retries, overriding calculated delay if it exceeds this. */
maxDelay?: number;
/** Factor for exponential growth (e.g., 2 for doubling). @default 2 */
exponent?: number;
/** Factor for linear growth (delay = base * factor * attempt). @default 1 */
factor?: number;
/** Apply random jitter to calculated delays to prevent thundering herd. @default true */
jitter?: boolean;
/**
* Maximum number of consecutive retries allowed after errors before terminating the poll.
* If undefined, retries continue until other termination conditions are met.
*/
maxRetries?: number;
}
/** Configuration options for a polling operation. */
export interface PollingOptions<T = any> {
/** The URL endpoint to poll. */
url: string;
/** HTTP method for polling request. @default 'GET' */
method?: 'GET' | 'POST';
/** Optional headers for the polling request. */
headers?: Record<string, string>;
/** Optional data for POST polling requests. */
data?: any;
/** Base interval (in ms) between successful polls, or before the first poll if initialDelay is not set. */
interval: number;
/** Configuration for backoff strategy on errors. Can be strategy name for defaults or full options object. */
backoff?: BackoffOptions | BackoffStrategyName;
/** Maximum number of total poll attempts (including retries) before termination. */
maxAttempts?: number;
/** Maximum total time (in ms) the polling operation should run before termination. */
pollingTimeout?: number;
/**
* A function called with the successful response data and status.
* Return true to terminate polling, false to continue.
*/
terminateWhen: (data: T, status: number) => boolean;
/**
* If true, polling automatically terminates on any 2xx success status,
* unless terminateWhen returns false explicitly.
* @default true
*/
terminateOnSuccessStatus?: boolean;
// Callbacks
/** Called on each successful (2xx) poll response. */
onSuccess?: (data: T, status: number, handle: PollingHandle) => void;
/** Called on each failed poll attempt (network error or non-2xx status). */
onError?: (error: StandardError, attempt: number, handle: PollingHandle) => void;
/** Called exactly once when polling stops for any reason. */
onTerminate?: (reason: TerminationReason, lastResult?: T | StandardError, handle: PollingHandle) => void;
/** If true, polling will automatically pause when network status is offline. @default true */
pauseWhenOffline?: boolean;
/** Delay (in ms) before the very first poll attempt. @default 0 */
initialDelay?: number;
/** Additional configuration to pass directly to the underlying httpClient request. */
httpClientConfig?: Omit<HttpClientRequestConfig, 'url' | 'method' | 'data' | 'headers' | 'signal'>;
}
/** Handle returned by startPolling to control the polling operation. */
export interface PollingHandle {
/** Unique identifier for this polling instance. */
readonly id: string;
/** Stops the polling operation immediately. Triggers onTerminate with CANCELLED or MANUAL_STOP. */
stop(): void;
/** Internal AbortController signal for cancelling HTTP requests. */
// readonly abortController: AbortController; // Maybe keep internal
}
/** Reason why a polling operation terminated. */
export enum TerminationReason {
CONDITION_MET = 'condition_met', // terminateWhen returned true or success occurred
MAX_ATTEMPTS_REACHED = 'max_attempts_reached',
POLLING_TIMEOUT = 'polling_timeout', // Max duration reached
CANCELLED = 'cancelled', // Internal cancellation (e.g., during cleanup)
MANUAL_STOP = 'manual_stop', // User called handle.stop()
ERROR_LIMIT_REACHED = 'error_limit_reached', // Exceeded backoff.maxRetries
INITIALIZATION_ERROR = 'initialization_error' // Error during setup
}
// --- Polling Service Export (/packages/core/src/polling/index.ts) ---
/**
* Starts a polling operation based on the provided options.
* @param options Configuration for the polling operation.
* @returns A PollingHandle to control the operation.
*/
export declare function startPolling<T = any>(options: PollingOptions<T>): PollingHandle;
// --- Internal Interfaces ---
/** Internal interface for backoff strategy implementations. */
interface IBackoffStrategy {
/** Calculates the delay for the given attempt number. */
calculateDelay(attempt: number): number;
/** Resets any internal state (like attempt count specific to backoff). */
reset(): void;
}-
Unit Tests: Test timer scheduling, backoff calculations, termination logic, callback invocation, cancellation logic, offline pause/resume logic using mocks (
httpClient,INetworkInfo, timers). -
Integration Tests: Test full polling lifecycle with mocked HTTP/Network. Test error backoff sequence. Test termination conditions. Test cancellation. Verify auth handled by underlying
httpClient. Test behavior during token refresh. VerifyactivePollsstate updates. - Edge Cases: Zero interval, immediate termination, rapid network changes, cancellation races.
-
startPollingcorrectly uses thehttpClientinstance for requests, passing relevant config and the internalAbortSignal. - Authentication is handled transparently by
httpClientinterceptors during poll requests (including refresh cycles).- If a token refresh occurs during a poll request, the poll attempt should fail (handled by
onError) and potentially retry according to backoff rules after the refresh completes.
- If a token refresh occurs during a poll request, the poll attempt should fail (handled by
-
startPollingcorrectly usesINetworkInfovia the platform manager to implementpauseWhenOffline. - Cancellation via
PollingHandle.stop()successfully aborts the in-flight HTTP request via the signal passed tohttpClient. - The
operations.activePollsslice in the internal state store is correctly updated (add on start, remove on terminate/stop). - Callbacks (
onSuccess,onError,onTerminate) are invoked correctly.
-
Internal: Comments in
pollingService.tsexplaining the state machine/loop, timer handling, backoff integration, cancellation flow, offline handling. -
External:
-
Guide: How to use
startPolling, full explanation ofPollingOptions(interval, backoff configs, termination, callbacks). - Examples: Common polling scenarios (check job status until 'completed', poll with error backoff).
-
Control: How to use the
PollingHandletostop()polling.
-
Guide: How to use
-
Changelog: Document the
startPollingAPI and its options.
- Focus on reliable timers, backoff, termination, and cancellation.
- Keep network awareness simple (
pauseWhenOffline). Avoid complex background/battery awareness for V1. - Ensure proper cleanup of timers, abort controllers, and network listeners on termination/stop.
- Jitter in backoff is important.
- Core logic is platform-agnostic.
- Relies on platform-agnostic
httpClientandINetworkInfo. - Uses standard JavaScript timers (
setTimeout/clearTimeout) andAbortController, which are available in both environments.