Collection of utility functions for Fluid Framework including async operations, data structures, performance monitoring, and cross-platform compatibility.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Comprehensive async utilities including promises, delays, timers, and promise caching with expiration support. Essential tools for managing asynchronous operations in Fluid Framework applications.
Simple promise-based delay utility for introducing pauses in async operations.
/**
* Returns a Promise that resolves after the specified number of milliseconds
* @param timeMs - Delay time in milliseconds
* @returns Promise that resolves after the delay
* @deprecated Moved to @fluidframework/core-utils
*/
function delay(timeMs: number): Promise<void>;Usage Examples:
import { delay } from "@fluidframework/common-utils";
// Simple delay
await delay(1000); // Wait 1 second
// Retry with delay
async function retryWithDelay<T>(fn: () => Promise<T>, maxRetries: number = 3): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
await delay(1000 * Math.pow(2, i)); // Exponential backoff
}
}
throw new Error("Max retries exceeded");
}
// Rate limiting
async function processItems<T>(items: T[], processFn: (item: T) => Promise<void>) {
for (const item of items) {
await processFn(item);
await delay(100); // 100ms between items
}
}Promise with external resolve/reject control, useful for bridging callback-based APIs with async/await.
/**
* Promise with external resolve/reject control
* Provides access to resolve and reject functions outside the Promise constructor
*/
class Deferred<T> {
/** The underlying Promise */
readonly promise: Promise<T>;
/** Whether the promise has been resolved or rejected */
readonly isCompleted: boolean;
/**
* Resolves the promise with the given value
* @param value - Value to resolve with (can be T or PromiseLike<T>)
*/
resolve(value: T | PromiseLike<T>): void;
/**
* Rejects the promise with the given reason
* @param error - Rejection reason (typically an Error)
*/
reject(error: any): void;
}Usage Examples:
import { Deferred } from "@fluidframework/common-utils";
// Bridge callback to Promise
function readFileAsync(filename: string): Promise<string> {
const deferred = new Deferred<string>();
fs.readFile(filename, 'utf8', (err, data) => {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(data);
}
});
return deferred.promise;
}
// Manual promise control
const completionSignal = new Deferred<void>();
// Start some async work
processLongRunningTask().then(() => {
completionSignal.resolve();
}).catch(error => {
completionSignal.reject(error);
});
// Wait for completion elsewhere
await completionSignal.promise;
console.log("Task completed!");
// Check completion status
if (!completionSignal.isCompleted) {
console.log("Still waiting...");
}Promise that delays execution until the promise is actually used (then/catch/await).
/**
* Lazy-evaluated Promise implementation
* The executor function is only called when the Promise is first accessed
* Implements the full Promise interface
*/
class LazyPromise<T> implements Promise<T> {
/**
* Creates a new LazyPromise
* @param executor - Function that returns a Promise when called
*/
constructor(executor: () => Promise<T>);
// Promise interface methods
then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
): Promise<TResult1 | TResult2>;
catch<TResult = never>(
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
): Promise<T | TResult>;
finally(onfinally?: (() => void) | null): Promise<T>;
readonly [Symbol.toStringTag]: string;
}Usage Examples:
import { LazyPromise } from "@fluidframework/common-utils";
// Expensive operation that's only executed when needed
const expensiveData = new LazyPromise(async () => {
console.log("Starting expensive calculation..."); // Only logs when accessed
await delay(5000);
return performExpensiveCalculation();
});
// The calculation hasn't started yet
console.log("LazyPromise created");
// Now the calculation starts
const result = await expensiveData;
console.log("Got result:", result);
// Factory pattern with lazy evaluation
function createLazyResource<T>(factory: () => Promise<T>): LazyPromise<T> {
return new LazyPromise(factory);
}
const lazyDatabase = createLazyResource(async () => {
console.log("Connecting to database...");
return await connectToDatabase();
});
// Database connection only happens when first accessed
const users = await lazyDatabase.then(db => db.getUsers());Specialized cache for async work with expiration and error handling capabilities.
/**
* Cache expiry policy types
* @deprecated Moved to @fluidframework/core-utils
* @internal
*/
type PromiseCacheExpiry =
| { policy: "indefinite" }
| { policy: "absolute"; durationMs: number }
| { policy: "sliding"; durationMs: number };
/**
* Promise cache configuration options
* @deprecated Moved to @fluidframework/core-utils
* @internal
*/
interface PromiseCacheOptions {
/** Common expiration policy for all items added to this cache */
expiry?: PromiseCacheExpiry;
/** If the stored Promise is rejected with a particular error, should the given key be removed? */
removeOnError?: (error: any) => boolean;
}
/**
* Specialized cache for async work with expiration and error handling
* Caches both successful results and failed promises based on configuration
* @deprecated Moved to @fluidframework/core-utils
* @internal
*/
class PromiseCache<TKey, TResult> {
/**
* Create the PromiseCache with the given options, with the following defaults:
* expiry: indefinite, removeOnError: true for all errors
* @param options - Cache configuration options
*/
constructor(options?: PromiseCacheOptions);
/**
* Check if there's anything cached at the given key
* @param key - Cache key
* @returns True if key exists in cache
*/
has(key: TKey): boolean;
/**
* Get the Promise for the given key, or undefined if it's not found.
* Extend expiry if applicable.
* @param key - Cache key
* @returns Cached promise or undefined if not found/expired
*/
get(key: TKey): Promise<TResult> | undefined;
/**
* Remove the Promise for the given key, returning true if it was found and removed
* @param key - Cache key
* @returns True if key was found and removed
*/
remove(key: TKey): boolean;
/**
* Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
* Returns false if the cache already contained an entry at that key, and true otherwise.
* @param key - Cache key where to store the async work
* @param asyncWorkFn - The async work to do and store, if not already in progress under the given key
* @returns False if cache already contained entry at key, true otherwise
*/
add(key: TKey, asyncWorkFn: () => Promise<TResult>): boolean;
/**
* Try to add the result of the given asyncFn, without overwriting an existing cache entry at that key.
* Returns a Promise for the added or existing async work being done at that key.
* @param key - Cache key name where to store the async work
* @param asyncWorkFn - The async work to do and store, if not already in progress under the given key
* @returns Promise for the added or existing async work
*/
addOrGet(key: TKey, asyncWorkFn: () => Promise<TResult>): Promise<TResult>;
/**
* Try to add the given value, without overwriting an existing cache entry at that key.
* Returns false if the cache already contained an entry at that key, and true otherwise.
* @param key - Cache key name where to store the value
* @param value - Value to store
* @returns False if cache already contained entry at key, true otherwise
*/
addValue(key: TKey, value: TResult): boolean;
/**
* Try to add the given value, without overwriting an existing cache entry at that key.
* Returns a Promise for the added or existing async work being done at that key.
* @param key - Cache key name where to store the async work
* @param value - Value to store
* @returns Promise for the added or existing async work
*/
addValueOrGet(key: TKey, value: TResult): Promise<TResult>;
}Usage Examples:
import { PromiseCache } from "@fluidframework/common-utils";
// Create cache with error handling
const apiCache = new PromiseCache<string, any>(
(failedKey) => {
console.log(`Removing failed request for: ${failedKey}`);
},
{
expiry: { policy: "absolute", durationMs: 5 * 60 * 1000 } // 5 minutes
}
);
// Cache API calls
async function fetchUserData(userId: string): Promise<User> {
return apiCache.addOrGet(userId, async () => {
console.log(`Fetching user ${userId} from API...`);
const response = await fetch(`/api/users/${userId}`);
return response.json();
});
}
// First call hits the API
const user1 = await fetchUserData("123");
// Second call uses cache
const user1Again = await fetchUserData("123"); // No API call
// Sliding window cache for frequently accessed data
const slideCache = new PromiseCache<string, string>(
(key) => console.log(`Evicted: ${key}`),
{ expiry: { policy: "sliding", durationMs: 30000 } } // 30 seconds
);
// Cache with indefinite expiry (manual removal only)
const permanentCache = new PromiseCache<string, Config>(
(key) => console.log(`Config error: ${key}`),
{ expiry: { policy: "indefinite" } }
);
// Pre-populate cache with known values
await permanentCache.addValue("app-config", getDefaultConfig());
// Complex caching scenario
class DataService {
private cache = new PromiseCache<string, any>(
(key) => this.handleCacheError(key),
{ expiry: { policy: "absolute", durationMs: 60000 } }
);
async getData(key: string): Promise<any> {
return this.cache.addOrGet(key, () => this.fetchFromSource(key));
}
private async fetchFromSource(key: string): Promise<any> {
// Expensive data fetching logic
return { data: `Fetched data for ${key}`, timestamp: Date.now() };
}
private handleCacheError(key: string): void {
console.error(`Cache error for key: ${key}`);
// Could implement retry logic, metrics, etc.
}
}Timer classes and utility functions for handling timeouts with support for very long durations.
/**
* Sets timeouts like the setTimeout function allowing timeouts to exceed the setTimeout's max timeout limit.
* @param timeoutFn - Executed when the timeout expires
* @param timeoutMs - Duration of the timeout in milliseconds
* @param setTimeoutIdFn - Executed to update the timeout if multiple timeouts are required when timeoutMs greater than maxTimeout
* @returns The initial timeout
* @deprecated Moved to @fluidframework/core-utils
* @internal
*/
function setLongTimeout(
timeoutFn: () => void,
timeoutMs: number,
setTimeoutIdFn?: (timeoutId: ReturnType<typeof setTimeout>) => void
): ReturnType<typeof setTimeout>;
/**
* Timer interface for basic timer operations
* @deprecated Moved to @fluidframework/core-utils
* @internal
*/
interface ITimer {
/** True if timer is currently running */
readonly hasTimer: boolean;
/** Starts the timer */
start(): void;
/** Cancels the timer if already running */
clear(): void;
}
/**
* Result interface for promise-based timers
* @deprecated Moved to @fluid-private/client-utils
* @internal
*/
interface IPromiseTimerResult {
timerResult: "timeout" | "cancel";
}
/**
* Timer interface that returns promises for completion
* @deprecated Moved to @fluid-private/client-utils
* @internal
*/
interface IPromiseTimer extends ITimer {
/** Starts the timer and returns a promise that resolves when the timer times out or is canceled */
start(): Promise<IPromiseTimerResult>;
}
/**
* Timer class for managing timeouts with support for long durations
* @deprecated Moved to @fluidframework/core-utils
* @internal
*/
class Timer implements ITimer {
constructor(
defaultTimeout: number,
defaultHandler: () => void,
getCurrentTick?: () => number
);
readonly hasTimer: boolean;
start(ms?: number, handler?: () => void): void;
clear(): void;
restart(ms?: number, handler?: () => void): void;
}
/**
* Promise-based timer that resolves when timeout completes or is canceled
* @deprecated Moved to @fluid-private/client-utils
* @internal
*/
class PromiseTimer implements IPromiseTimer {
constructor(defaultTimeout: number, defaultHandler: () => void);
readonly hasTimer: boolean;
start(ms?: number, handler?: () => void): Promise<IPromiseTimerResult>;
clear(): void;
}Usage Examples:
import { Timer, PromiseTimer, setLongTimeout } from "@fluidframework/common-utils";
// Basic timer with long timeout support
const timer = new Timer(5000, () => console.log("Timer fired!"));
timer.start();
// Override default timeout and handler
timer.start(10000, () => console.log("Custom handler"));
// Promise-based timer
const promiseTimer = new PromiseTimer(3000, () => console.log("Background task"));
const result = await promiseTimer.start();
console.log(`Timer completed with: ${result.timerResult}`);
// Long timeout (exceeds setTimeout's 24.8 day limit)
setLongTimeout(
() => console.log("Very long timeout completed"),
30 * 24 * 60 * 60 * 1000, // 30 days
(timeoutId) => console.log(`Timeout scheduled: ${timeoutId}`)
);
// Timer-based retry mechanism
class RetryTimer {
private timer = new Timer(1000, () => this.retry());
private retries = 0;
private maxRetries = 3;
async executeWithRetry<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn();
} catch (error) {
if (this.retries < this.maxRetries) {
console.log(`Attempt ${this.retries + 1} failed, retrying...`);
this.retries++;
this.timer.start(1000 * Math.pow(2, this.retries)); // Exponential backoff
throw error; // Re-throw to handle in calling code
} else {
throw new Error(`Failed after ${this.maxRetries} retries`);
}
}
}
private retry(): void {
console.log("Retrying operation...");
}
}import { Deferred, LazyPromise, PromiseCache, delay } from "@fluidframework/common-utils";
// Circuit breaker pattern with cache
class CircuitBreaker<TKey, TResult> {
private cache = new PromiseCache<TKey, TResult>(
(key) => this.onFailure(key),
{ expiry: { policy: "sliding", durationMs: 30000 } }
);
private failureCount = 0;
private lastFailure: number = 0;
async execute(key: TKey, fn: () => Promise<TResult>): Promise<TResult> {
if (this.isCircuitOpen()) {
throw new Error("Circuit breaker is open");
}
return this.cache.addOrGet(key, async () => {
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure(key);
throw error;
}
});
}
private isCircuitOpen(): boolean {
return this.failureCount >= 5 && (Date.now() - this.lastFailure) < 60000;
}
private onSuccess(): void {
this.failureCount = 0;
}
private onFailure(key: TKey): void {
this.failureCount++;
this.lastFailure = Date.now();
}
}
// Coordinated async operations
async function coordinatedStartup(): Promise<void> {
const dbReady = new Deferred<void>();
const cacheReady = new Deferred<void>();
// Start services concurrently
const dbInit = new LazyPromise(async () => {
await initializeDatabase();
dbReady.resolve();
});
const cacheInit = new LazyPromise(async () => {
await initializeCache();
cacheReady.resolve();
});
// Start initialization
void dbInit;
void cacheInit;
// Wait for both to complete
await Promise.all([dbReady.promise, cacheReady.promise]);
console.log("All services ready!");
}These async utilities provide the foundation for building robust, scalable asynchronous operations in Fluid Framework applications.