or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assertions.mdasync-operations.mdbuffer-operations.mddata-structures.mdencoding-hashing.mdevent-handling.mdindex.mdperformance-monitoring.mdutility-functions.md
tile.json

async-operations.mddocs/

Async Operations

Comprehensive async utilities including promises, delays, timers, and promise caching with expiration support. Essential tools for managing asynchronous operations in Fluid Framework applications.

Capabilities

Delay Function

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
  }
}

Deferred Class

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...");
}

LazyPromise Class

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());

PromiseCache Class

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 Utilities

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...");
  }
}

Advanced Patterns

Combining Async Utilities

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.