CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-node-fetch

A light-weight module that brings Fetch API to Node.js

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

error-handling.mddocs/

Error Handling

Specialized error classes for different types of failures during HTTP operations, providing detailed error information for proper debugging and error recovery.

Capabilities

FetchError Class

Operational error class for network failures, timeouts, and other fetch-related issues.

/**
 * Error class for fetch operation failures
 */
class FetchError extends Error {
  constructor(message: string, type: string, systemError?: Record<string, unknown>);
  
  readonly name: 'FetchError';
  readonly type: string;
  readonly code?: string;
  readonly errno?: string;
  readonly erroredSysCall?: string;
}

Common Error Types:

  • 'system' - Network-level errors (DNS resolution, connection failures)
  • 'max-redirect' - Too many redirects encountered
  • 'max-size' - Response body exceeded size limit
  • 'invalid-redirect' - Invalid redirect URL
  • 'no-redirect' - Redirect encountered when redirect: 'error'
  • 'unsupported-redirect' - Cannot redirect with non-replayable body

Usage Examples:

import fetch, { FetchError } from 'node-fetch';

try {
  const response = await fetch('https://nonexistent-domain.example');
} catch (error) {
  if (error instanceof FetchError) {
    console.log('Fetch error type:', error.type);
    console.log('Error message:', error.message);
    
    switch (error.type) {
      case 'system':
        console.log('Network error:', error.code);
        if (error.code === 'ENOTFOUND') {
          console.log('Domain not found');
        } else if (error.code === 'ECONNREFUSED') {
          console.log('Connection refused');
        }
        break;
        
      case 'max-redirect':
        console.log('Too many redirects');
        break;
        
      case 'max-size':
        console.log('Response too large');
        break;
    }
  }
}

AbortError Class

Error class specifically for cancelled/aborted requests using AbortSignal.

/**
 * Error class for aborted requests
 */
class AbortError extends Error {
  constructor(message: string, type?: string);
  
  readonly name: 'AbortError';
  readonly type: string;
}

Usage Examples:

import fetch, { AbortError } from 'node-fetch';

// Request cancellation with timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

try {
  const response = await fetch('https://api.example.com/slow-endpoint', {
    signal: controller.signal
  });
  
  clearTimeout(timeoutId);
  const data = await response.json();
  
} catch (error) {
  if (error instanceof AbortError) {
    console.log('Request was cancelled:', error.message);
    console.log('Error type:', error.type); // 'aborted'
  } else {
    console.log('Other error:', error.message);
  }
}

System Error Details

When FetchError has type 'system', it includes Node.js system error details.

interface SystemErrorDetails {
  code?: string;        // System error code (ENOTFOUND, ECONNREFUSED, etc.)
  errno?: string;       // System error number
  erroredSysCall?: string; // System call that failed
}

Usage Examples:

try {
  await fetch('https://192.168.1.999:8080/api'); // Invalid IP
} catch (error) {
  if (error instanceof FetchError && error.type === 'system') {
    console.log('System error code:', error.code);
    console.log('System call:', error.erroredSysCall);
    console.log('Error number:', error.errno);
    
    // Handle specific system errors
    switch (error.code) {
      case 'ENOTFOUND':
        console.log('DNS lookup failed - domain not found');
        break;
      case 'ECONNREFUSED':
        console.log('Connection refused - server not accepting connections');
        break;
      case 'ETIMEDOUT':
        console.log('Connection timed out');
        break;
      case 'ECONNRESET':
        console.log('Connection reset by peer');
        break;
      default:
        console.log('Unknown system error:', error.code);
    }
  }
}

Error Handling Patterns

Common patterns for handling different types of errors in fetch operations.

interface ErrorHandlingPattern {
  handleNetworkError(error: FetchError): void;
  handleHttpError(response: Response): Promise<void>;
  retryWithBackoff(url: string, options?: RequestInit, maxRetries?: number): Promise<Response>;
}

Network Error Handling:

async function robustFetch(url, options = {}, retries = 3) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      return await fetch(url, options);
    } catch (error) {
      if (error instanceof FetchError) {
        console.log(`Attempt ${attempt} failed:`, error.type);
        
        // Don't retry certain error types
        if (error.type === 'max-size' || error.type === 'invalid-redirect') {
          throw error;
        }
        
        // Only retry system errors (network issues)
        if (error.type !== 'system' || attempt === retries) {
          throw error;
        }
        
        // Wait before retry with exponential backoff
        const delay = Math.pow(2, attempt - 1) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
        
      } else if (error instanceof AbortError) {
        // Don't retry aborted requests
        throw error;
      } else {
        throw error;
      }
    }
  }
}

HTTP Error Handling:

async function fetchWithErrorHandling(url, options = {}) {
  let response;
  
  try {
    response = await fetch(url, options);
  } catch (error) {
    if (error instanceof FetchError) {
      throw new Error(`Network error: ${error.message}`);
    } else if (error instanceof AbortError) {
      throw new Error('Request was cancelled');
    } else {
      throw error;
    }
  }
  
  // Handle HTTP error status codes
  if (!response.ok) {
    let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
    
    try {
      // Try to get error details from response body
      const contentType = response.headers.get('content-type');
      
      if (contentType?.includes('application/json')) {
        const errorBody = await response.json();
        errorMessage += ` - ${errorBody.message || errorBody.error || 'Unknown error'}`;
      } else if (contentType?.includes('text/')) {
        const errorText = await response.text();
        if (errorText.length < 200) { // Avoid logging huge HTML error pages
          errorMessage += ` - ${errorText}`;
        }
      }
    } catch (bodyError) {
      // Ignore errors reading response body
    }
    
    throw new Error(errorMessage);
  }
  
  return response;
}

Timeout Handling

Implementing request timeouts using AbortController and handling timeout errors.

interface TimeoutConfig {
  timeout: number;
  signal?: AbortSignal;
}

Usage Examples:

// Simple timeout wrapper
function fetchWithTimeout(url, options = {}, timeout = 5000) {
  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), timeout);
  
  return fetch(url, {
    ...options,
    signal: controller.signal
  }).finally(() => clearTimeout(id));
}

// Advanced timeout with custom error
class TimeoutError extends Error {
  constructor(timeout) {
    super(`Request timed out after ${timeout}ms`);
    this.name = 'TimeoutError';
    this.timeout = timeout;
  }
}

async function fetchWithCustomTimeout(url, options = {}, timeout = 10000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);
  
  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });
    
    clearTimeout(timeoutId);
    return response;
    
  } catch (error) {
    clearTimeout(timeoutId);
    
    if (error instanceof AbortError) {
      throw new TimeoutError(timeout);
    }
    throw error;
  }
}

// Usage with error handling
try {
  const response = await fetchWithCustomTimeout(
    'https://api.example.com/slow-endpoint',
    { method: 'GET' },
    3000
  );
  const data = await response.json();
} catch (error) {
  if (error instanceof TimeoutError) {
    console.log('Request timed out:', error.timeout);
  } else if (error instanceof FetchError) {
    console.log('Network error:', error.message);
  } else {
    console.log('Other error:', error.message);
  }
}

Redirect Error Handling

Special handling for redirect-related errors and manual redirect processing.

interface RedirectErrorHandling {
  handleRedirectError(error: FetchError): void;
  followRedirectsManually(url: string, options?: RequestInit): Promise<Response>;
}

Usage Examples:

// Handle redirect limits
try {
  const response = await fetch('https://example.com/redirect-chain', {
    follow: 3 // Limit to 3 redirects
  });
} catch (error) {
  if (error instanceof FetchError && error.type === 'max-redirect') {
    console.log('Too many redirects encountered');
    // Could implement manual redirect handling here
  }
}

// Manual redirect handling
async function followRedirectsManually(url, options = {}, maxRedirects = 5) {
  let currentUrl = url;
  let redirectCount = 0;
  
  while (redirectCount < maxRedirects) {
    const response = await fetch(currentUrl, {
      ...options,
      redirect: 'manual'
    });
    
    if (response.status >= 300 && response.status < 400) {
      const location = response.headers.get('location');
      if (!location) {
        throw new Error('Redirect response missing Location header');
      }
      
      currentUrl = new URL(location, currentUrl).toString();
      redirectCount++;
      console.log(`Redirect ${redirectCount}: ${currentUrl}`);
      
    } else {
      return response;
    }
  }
  
  throw new Error(`Too many redirects (${maxRedirects})`);
}

Error Recovery Strategies

Comprehensive error recovery patterns for robust HTTP clients.

interface ErrorRecoveryStrategy {
  exponentialBackoff(attempt: number): number;
  shouldRetry(error: Error): boolean;
  circuitBreaker(url: string): boolean;
}

Usage Examples:

// Circuit breaker pattern
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failures = new Map();
    this.threshold = threshold;
    this.timeout = timeout;
  }
  
  canExecute(url) {
    const failure = this.failures.get(url);
    if (!failure) return true;
    
    if (failure.count >= this.threshold) {
      if (Date.now() - failure.lastFailure < this.timeout) {
        return false; // Circuit open
      } else {
        // Try again after timeout
        this.failures.delete(url);
        return true;
      }
    }
    return true;
  }
  
  recordSuccess(url) {
    this.failures.delete(url);
  }
  
  recordFailure(url) {
    const failure = this.failures.get(url) || { count: 0, lastFailure: 0 };
    failure.count++;
    failure.lastFailure = Date.now();
    this.failures.set(url, failure);
  }
}

// Comprehensive error handling with circuit breaker
const circuitBreaker = new CircuitBreaker();

async function resilientFetch(url, options = {}, maxRetries = 3) {
  if (!circuitBreaker.canExecute(url)) {
    throw new Error('Circuit breaker is open for this URL');
  }
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetchWithTimeout(url, options, 10000);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      circuitBreaker.recordSuccess(url);
      return response;
      
    } catch (error) {
      console.log(`Attempt ${attempt} failed:`, error.message);
      
      // Don't retry certain errors
      if (error instanceof AbortError || 
          (error instanceof FetchError && error.type === 'max-size')) {
        circuitBreaker.recordFailure(url);
        throw error;
      }
      
      if (attempt === maxRetries) {
        circuitBreaker.recordFailure(url);
        throw error;
      }
      
      // Exponential backoff
      const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

docs

body-processing.md

error-handling.md

file-blob.md

headers.md

http-client.md

index.md

request-response.md

utilities.md

tile.json