CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-sentry--utils

Deprecated utilities package for Sentry JavaScript SDKs - all functionality moved to @sentry/core

Pending
Overview
Eval results
Files

async-utilities.mddocs/

Promise & Async Utilities

DEPRECATED: Import all functions from @sentry/core instead of @sentry/utils.

Synchronous promise implementation and promise buffer management for reliable async operations and controlled concurrency.

Capabilities

SyncPromise Class

Synchronous promise implementation that executes immediately without microtask scheduling.

/**
 * Synchronous promise implementation that executes immediately
 * @template T - Type of the resolved value
 */
class SyncPromise<T> implements PromiseLike<T> {
  /**
   * Creates a new SyncPromise
   * @param executor - Function that initializes the promise
   */
  constructor(
    executor: (
      resolve: (value: T | PromiseLike<T>) => void, 
      reject: (reason?: any) => void
    ) => void
  );
  
  /**
   * Attaches callbacks for resolution and/or rejection
   * @param onfulfilled - Callback for successful resolution
   * @param onrejected - Callback for rejection
   * @returns New SyncPromise with transformed value
   */
  then<TResult1 = T, TResult2 = never>(
    onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
    onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
  ): SyncPromise<TResult1 | TResult2>;
  
  /**
   * Attaches a callback for rejection
   * @param onrejected - Callback for rejection
   * @returns New SyncPromise
   */
  catch<TResult = never>(
    onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null,
  ): SyncPromise<T | TResult>;
  
  /**
   * Returns a resolved SyncPromise with the given value
   * @param value - Value to resolve with
   * @returns Resolved SyncPromise
   */
  static resolve<T>(value: T | PromiseLike<T>): SyncPromise<T>;
  
  /**
   * Returns a rejected SyncPromise with the given reason
   * @param reason - Reason for rejection
   * @returns Rejected SyncPromise
   */
  static reject<T = never>(reason?: any): SyncPromise<T>;
}

SyncPromise Factory Functions

Convenient factory functions for creating resolved and rejected SyncPromises.

/**
 * Creates a resolved SyncPromise with the given value
 * @param value - Value to resolve with
 * @returns Resolved SyncPromise
 */
function resolvedSyncPromise<T>(value: T): SyncPromise<T>;

/**
 * Creates a rejected SyncPromise with the given reason
 * @param reason - Reason for rejection
 * @returns Rejected SyncPromise
 */
function rejectedSyncPromise<T = never>(reason: any): SyncPromise<T>;

Usage Examples:

import { SyncPromise, resolvedSyncPromise, rejectedSyncPromise } from "@sentry/core";

// Basic SyncPromise usage - executes immediately
const syncPromise = new SyncPromise<number>((resolve, reject) => {
  console.log('Executing immediately!'); // Logs immediately, not on next tick
  resolve(42);
});

syncPromise.then(value => {
  console.log('Value:', value); // Logs immediately after resolve
});

// Factory function usage
const resolved = resolvedSyncPromise('success');
const rejected = rejectedSyncPromise(new Error('failed'));

// Chaining works like regular promises
const result = resolvedSyncPromise(10)
  .then(x => x * 2)
  .then(x => x + 5)
  .catch(err => console.error(err));

console.log(result); // SyncPromise, but value is computed immediately

// Converting async operations to sync for testing
function mockAsyncOperation(shouldFail: boolean): SyncPromise<string> {
  if (shouldFail) {
    return rejectedSyncPromise(new Error('Operation failed'));
  }
  return resolvedSyncPromise('Operation succeeded');
}

Promise Buffer

Managed buffer for controlling concurrent promise execution with limits.

/**
 * Buffer that manages concurrent promise execution
 * @template T - Type of promise results
 */
interface PromiseBuffer<T> {
  /** Array of currently running promises */
  readonly $: Array<PromiseLike<T>>;
  
  /**
   * Adds a task to the buffer, respecting concurrency limits
   * @param taskProducer - Function that creates the promise when ready to execute
   * @returns Promise that resolves when the task completes
   */
  add(taskProducer: () => PromiseLike<T>): PromiseLike<T>;
  
  /**
   * Waits for all currently running promises to complete
   * @param timeout - Optional timeout in milliseconds
   * @returns Promise that resolves to true if all tasks completed, false if timeout
   */
  drain(timeout?: number): PromiseLike<boolean>;
}

/**
 * Creates a new promise buffer with optional concurrency limit
 * @param limit - Maximum number of concurrent promises (default: 30)
 * @returns New promise buffer instance
 */
function makePromiseBuffer<T>(limit?: number): PromiseBuffer<T>;

Usage Examples:

import { makePromiseBuffer } from "@sentry/core";

// Create buffer with concurrency limit
const buffer = makePromiseBuffer<string>(3); // Max 3 concurrent operations

// Add tasks to buffer - they'll execute when slots are available
const tasks = Array.from({ length: 10 }, (_, i) => 
  buffer.add(() => fetchData(`item-${i}`))
);

// Wait for all tasks to complete
Promise.all(tasks).then(results => {
  console.log('All tasks completed:', results);
});

// Example: Processing file uploads with concurrency control
class FileUploader {
  private uploadBuffer = makePromiseBuffer<UploadResult>(5);
  
  async uploadFiles(files: File[]): Promise<UploadResult[]> {
    const uploadTasks = files.map(file => 
      this.uploadBuffer.add(() => this.uploadSingleFile(file))
    );
    
    return Promise.all(uploadTasks);
  }
  
  private async uploadSingleFile(file: File): Promise<UploadResult> {
    // Simulate file upload
    const formData = new FormData();
    formData.append('file', file);
    
    const response = await fetch('/upload', {
      method: 'POST',
      body: formData
    });
    
    return response.json();
  }
  
  async waitForAllUploads(timeout = 30000): Promise<boolean> {
    return this.uploadBuffer.drain(timeout);
  }
}

Watchdog Timer

Utility for creating timeout mechanisms and operation monitoring.

/**
 * Creates a watchdog timer that can be used to timeout operations
 * @param callback - Function to call when timer expires
 * @param delay - Delay in milliseconds before calling callback
 * @returns Function to cancel the timer
 */
function watchdogTimer(callback: () => void, delay: number): () => void;

Usage Examples:

import { watchdogTimer } from "@sentry/core";

// Basic timeout functionality
function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
  return new Promise((resolve, reject) => {
    const cancel = watchdogTimer(() => {
      reject(new Error(`Operation timed out after ${timeoutMs}ms`));
    }, timeoutMs);
    
    promise
      .then(resolve)
      .catch(reject)
      .finally(cancel); // Cancel timer when promise settles
  });
}

// Usage with async operations
async function fetchWithTimeout(url: string): Promise<Response> {
  const fetchPromise = fetch(url);
  return withTimeout(fetchPromise, 5000); // 5 second timeout
}

// Periodic health checks
class HealthMonitor {
  private cancelWatchdog?: () => void;
  
  startMonitoring(intervalMs: number) {
    const scheduleNext = () => {
      this.cancelWatchdog = watchdogTimer(() => {
        this.performHealthCheck()
          .then(() => scheduleNext()) // Schedule next check
          .catch(err => console.error('Health check failed:', err));
      }, intervalMs);
    };
    
    scheduleNext();
  }
  
  stopMonitoring() {
    this.cancelWatchdog?.();
  }
  
  private async performHealthCheck(): Promise<void> {
    // Health check logic
    const response = await fetch('/health');
    if (!response.ok) {
      throw new Error(`Health check failed: ${response.status}`);
    }
  }
}

Async Patterns

Converting Regular Promises to SyncPromises

Pattern for testing and synchronous execution:

import { SyncPromise, resolvedSyncPromise, rejectedSyncPromise } from "@sentry/core";

function toSyncPromise<T>(promise: Promise<T>): SyncPromise<T> {
  // Note: This breaks the asynchronous nature - use carefully
  let result: T;
  let error: any;
  let isResolved = false;
  let isRejected = false;
  
  promise
    .then(value => {
      result = value;
      isResolved = true;
    })
    .catch(err => {
      error = err;
      isRejected = true;
    });
  
  // Warning: This is a synchronous check and won't work for truly async operations
  if (isResolved) {
    return resolvedSyncPromise(result);
  } else if (isRejected) {
    return rejectedSyncPromise(error);
  } else {
    // For truly async operations, this approach won't work
    throw new Error('Cannot convert async promise to sync');
  }
}

Controlled Batch Processing

Using promise buffers for controlled batch processing:

import { makePromiseBuffer } from "@sentry/core";

class BatchProcessor<T, R> {
  private buffer = makePromiseBuffer<R>(10);
  
  async processBatch(items: T[], processor: (item: T) => Promise<R>): Promise<R[]> {
    const tasks = items.map(item => 
      this.buffer.add(() => processor(item))
    );
    
    return Promise.all(tasks);
  }
  
  async processWithRetry<T, R>(
    items: T[], 
    processor: (item: T) => Promise<R>,
    maxRetries = 3
  ): Promise<Array<R | Error>> {
    const results: Array<R | Error> = [];
    
    for (const item of items) {
      const processWithRetry = async (): Promise<R> => {
        let lastError: Error;
        
        for (let attempt = 0; attempt <= maxRetries; attempt++) {
          try {
            return await processor(item);
          } catch (error) {
            lastError = error as Error;
            if (attempt < maxRetries) {
              await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
            }
          }
        }
        
        throw lastError!;
      };
      
      try {
        const result = await this.buffer.add(processWithRetry);
        results.push(result);
      } catch (error) {
        results.push(error as Error);
      }
    }
    
    return results;
  }
}

Circuit Breaker Pattern

Using watchdog timers for circuit breaker implementation:

import { watchdogTimer } from "@sentry/core";

class CircuitBreaker {
  private failureCount = 0;
  private lastFailureTime = 0;
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
  
  constructor(
    private failureThreshold = 5,
    private timeout = 60000, // 1 minute
    private retryTimeout = 10000 // 10 seconds
  ) {}
  
  async execute<T>(operation: () => Promise<T>): Promise<T> {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime < this.retryTimeout) {
        throw new Error('Circuit breaker is OPEN');
      } else {
        this.state = 'HALF_OPEN';
      }
    }
    
    try {
      const result = await this.executeWithTimeout(operation);
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  private executeWithTimeout<T>(operation: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      const cancel = watchdogTimer(() => {
        reject(new Error('Operation timed out'));
      }, this.timeout);
      
      operation()
        .then(resolve)
        .catch(reject)
        .finally(cancel);
    });
  }
  
  private onSuccess(): void {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }
  
  private onFailure(): void {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
    }
  }
}

Types

interface UploadResult {
  filename: string;
  size: number;
  url: string;
  success: boolean;
}

Migration Note: All promise utilities have been moved from @sentry/utils to @sentry/core. Update your imports accordingly.

Install with Tessl CLI

npx tessl i tessl/npm-sentry--utils

docs

async-utilities.md

data-processing.md

envelopes.md

environment.md

error-handling.md

index.md

instrumentation.md

logging.md

stack-processing.md

type-guards.md

tile.json