Skip to content

Instantly share code, notes, and snippets.

@composite
Created March 4, 2025 08:09
Show Gist options
  • Save composite/c785ec18aecf4303a86098c068d41e6b to your computer and use it in GitHub Desktop.
Save composite/c785ec18aecf4303a86098c068d41e6b to your computer and use it in GitHub Desktop.
AI Generated utility functions - Typescript friendly, tested.
/**
* Definition of the animation frame callback function type
*/
type AnimationFrameCallback = (time: number) => void;
/**
* Definition of the cancel function type
*/
type CancelFunction = () => void;
/**
* Interface for the animation frame wrapper function
*/
interface AnimationFrameWrapper {
(callback: AnimationFrameCallback): CancelFunction;
}
/**
* Factory function in singleton style for requestAnimationFrame
*
* - Ensures callbacks are executed sequentially
* - Provides cancellation functionality through a subscription pattern
* - Maintains core functionality even if an error occurs during callback execution
* - Manages a callback queue to prevent concurrent execution
*
* @returns A function that wraps requestAnimationFrame
*/
function createAnimationFrameManager(): AnimationFrameWrapper {
// Animation frame state management
let isRunning = false;
let requestId: number | null = null;
let queue: Array<{ callback: AnimationFrameCallback; id: number }> = [];
let nextId = 0;
/**
* Function to process callbacks in the queue sequentially
*/
const processQueue = (timestamp: number) => {
isRunning = true;
if (queue.length === 0) {
isRunning = false;
requestId = null;
return;
}
// Get the next callback from the queue
const { callback, id } = queue.shift()!;
let error: any = null;
try {
// Execute the callback
callback(timestamp);
} catch (err) {
// Store the error for later handling
error = err;
}
// Schedule the next frame
if (queue.length > 0) {
requestId = requestAnimationFrame(processQueue);
} else {
isRunning = false;
requestId = null;
}
// If an error occurred, throw it after the next frame is scheduled
if (error) {
console.error('Animation frame callback error:', error);
throw error;
}
};
/**
* requestAnimationFrame wrapper function
* Implements a subscription pattern by registering a callback and returning a cancel function
*/
const requestAnimationFrameWrapper = (callback: AnimationFrameCallback): CancelFunction => {
const id = nextId++;
// Add the callback to the queue
queue.push({ callback, id });
// Start processing the queue if it’s not already running
if (!isRunning && requestId === null) {
requestId = requestAnimationFrame(processQueue);
}
// Return a function to cancel this specific request
return () => {
// Remove this callback from the queue
queue = queue.filter(item => item.id !== id);
// If the queue is empty and not running, cancel the pending animation frame
if (queue.length === 0 && requestId !== null && !isRunning) {
cancelAnimationFrame(requestId);
requestId = null;
}
};
};
return requestAnimationFrameWrapper;
}
// Usage example:
/*
const animationFrame = createAnimationFrameManager();
// Subscribe to an animation frame
const cancel = animationFrame((timestamp) => {
console.log('Animation frame time:', timestamp);
// Perform animation tasks here
});
// Cancel the subscription later
cancel();
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment