Background Commands
Hosanna implements a robust system for handling background commands and asynchronous operations through its AsyncCommand framework. This documentation explains how background commands work and how to use them effectively in your application.
Live Example
Ui And Interaction Background Commands
@view('UiAndInteractionBackgroundCommands')
export class UiAndInteractionBackgroundCommandsView extends BaseExampleScreenView<UiAndInteractionBackgroundCommandsState> {
@state stateText = '';
@state result = '';
protected getViews(): ViewStruct<ViewState>[] {
// --- edit below ---
return [
VGroup([
Button({ text: 'Dispatch Background HTTP' })
.isInitialFocus(true)
.onClick(() => this.dispatchBackgroundHttp()),
Label({ text: `State: ${this.stateText}` }),
Label({ text: this.result, width: 1000, wrap: true }),
]).itemSpacing(16)
.translation([50, 50])
]
// --- edit above ---
}
private dispatchBackgroundHttp() {
this.stateText = 'loading';
this.result = '';
const url = 'https://jsonplaceholder.typicode.com/posts/2';
return this.dispatch<IHsFetchResponse>(HttpMethod.Get, { url })
.then((response) => {
this.stateText = 'completed';
this.result = JSON.stringify(response.json);
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.catch((reason: any) => {
this.stateText = 'failed';
this.result = String(reason);
});
}
}Dispatch a background command and watch the UI handle progress and completion.
Core Components
1. AsyncCommand
The AsyncCommand class is the fundamental building block for background operations in Hosanna. It provides:
- Unique command identification
- Lifecycle management (dispatch, cancel, pause, resume)
- Progress tracking
- State management
- Promise-based execution
2. Command Structure
Each background command consists of:
-
ID: A unique identifier for tracking
-
Scope: The command's operational context
-
Handler: The specific operation handler
-
Arguments: Optional parameters for the command
-
State: Current execution state
-
Progress: Completion progress (0.0 to 1.0)
Using Background Commands
1. Creating a Command Handler
@commandCategory('DataLoader')
class LoadDataHandler {
@command('LoadJsonData')
loadJsonData(payload: any): HsPromise<any> {
// Implement async operation
}
}
2. Dispatching Commands
You can dispatch background commands from views using:
this.dispatchAsync('DataLoader', 'LoadJsonData', { url: '/data.json' })
.then((result) => {
// Handle success
})
.catch((error) => {
// Handle error
});
Passing function pointers across tasks
Sometimes you need to pass a function to be executed in a background task. Use AsyncFunctionPointer (module + export name) to reference the function safely across node/task boundaries.
AsyncFunctionPointer cannot be inline or anonymous functions — they must be exported functions defined at module scope. The function must be exported so it can be serialized as a string pointer and resolved across boundaries.
// Correct: export the function first
// transformFunctions.ts
export function processRows(response: IHsFetchResponse) {
// ... processing logic
}
// Then import and use it
import { processRows } from './transformFunctions';
this.dispatch<IHsFetchResponse>(Http.Get, {
url: 'https://api.example.com/data',
transformFunction: processRows // Exported function reference
});
Signature:
// hosanna-api
declare type AsyncFunctionPointer = (...args: any[]) => any;
declare function hs_resolveAsyncFunctionPointer(ptr: AsyncFunctionPointer): Function;
The runtime can retrieve the function later (e.g., via a helper like getAsyncFunctionFromPointer) and execute it inside a task, avoiding closure capture issues.
Hosanna's linter reports a diagnostic if you pass an inline function or use .bind(this) where an AsyncFunctionPointer is expected (for example, HsFetchOptions.transformFunction or postProcessFunction). Export the function at module scope and pass that exported symbol instead.
Command Lifecycle
Background commands go through several states:
- Initializing: Command is being created
- Running: Command is actively executing
- Paused: Command execution is temporarily suspended
- Completed: Command has finished successfully
- Failed: Command encountered an error
- Cancelled: Command was manually cancelled
Integration with Async Framework
Hosanna's background commands integrate with:
- AsyncManager: Central controller for async tasks
- HsPromise: Custom promise implementation
- TimerService: Manages timing and scheduling
- PortPromise: Platform-specific promise implementation (e.g., for Roku)
Best Practices
-
Error Handling
- Always implement proper error handling in command handlers
- Use try/catch blocks for potential failures
- Provide meaningful error messages
-
Progress Tracking
- Update progress for long-running operations
- Use progress callbacks for UI updates
-
Resource Management
- Properly clean up resources when commands complete
- Cancel long-running commands when no longer needed
-
Platform Considerations
- Use platform-specific implementations when necessary
- Consider memory constraints on different devices
Example Implementation
// Define a background command handler
@commandCategory('MediaLoader')
class MediaLoadHandler {
@command('LoadVideo')
async loadVideo(payload: {url: string}): HsPromise<void> {
try {
// Initialize progress
this.progress = 0;
// Perform async operation
const result = await fetch(payload.url);
// Update progress
this.progress = 0.5;
// Process result
const videoData = await result.json();
// Complete operation
this.progress = 1.0;
return videoData;
} catch (error) {
throw new Error(`Failed to load video: ${error.message}`);
}
}
}
// Using the command in a view
class VideoPlayerView extends BaseView {
loadVideo(url: string) {
this.dispatchAsync('MediaLoader', 'LoadVideo', { url })
.then(videoData => this.playVideo(videoData))
.catch(error => this.handleError(error));
}
}
Debugging Background Commands
For debugging background commands, Hosanna provides:
- Command execution logging
- State tracking
- Progress monitoring
- Error reporting
You can use the Remote Debug Panel to monitor and debug background commands during development.