CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-undici

An HTTP/1.1 client, written from scratch for Node.js

Pending
Overview
Eval results
Files

errors.mddocs/

Error Handling

Comprehensive error classes for different types of HTTP client failures with detailed error information and proper error handling patterns.

Capabilities

Error Classes

Undici provides specific error types for different failure scenarios to enable precise error handling.

/**
 * All undici error types
 */
const errors: {
  UndiciError: typeof UndiciError;
  ConnectTimeoutError: typeof ConnectTimeoutError;
  HeadersTimeoutError: typeof HeadersTimeoutError;
  BodyTimeoutError: typeof BodyTimeoutError;
  ResponseError: typeof ResponseError;
  ResponseStatusCodeError: typeof ResponseStatusCodeError;
  RequestRetryError: typeof RequestRetryError;
  ClientDestroyedError: typeof ClientDestroyedError;
  ClientClosedError: typeof ClientClosedError;
  SocketError: typeof SocketError;
  NotSupportedError: typeof NotSupportedError;
  InvalidArgumentError: typeof InvalidArgumentError;
  InvalidReturnValueError: typeof InvalidReturnValueError;
  RequestAbortedError: typeof RequestAbortedError;
  InformationalError: typeof InformationalError;
  RequestContentLengthMismatchError: typeof RequestContentLengthMismatchError;
  ResponseContentLengthMismatchError: typeof ResponseContentLengthMismatchError;
  HeadersOverflowError: typeof HeadersOverflowError;
  HTTPParserError: typeof HTTPParserError;
};

Base Error Class

/**
 * Base class for all undici errors
 */
class UndiciError extends Error {
  name: string;
  code: string;
  message: string;
}

Network Timeout Errors

Errors related to network timeouts during different phases of the request.

/**
 * Connection establishment timeout
 */
class ConnectTimeoutError extends UndiciError {
  name: 'ConnectTimeoutError';
  code: 'UND_ERR_CONNECT_TIMEOUT';
  message: string;
}

/**
 * Headers reception timeout
 */
class HeadersTimeoutError extends UndiciError {
  name: 'HeadersTimeoutError';
  code: 'UND_ERR_HEADERS_TIMEOUT';
  message: string;
}

/**
 * Body reception timeout
 */
class BodyTimeoutError extends UndiciError {
  name: 'BodyTimeoutError';
  code: 'UND_ERR_BODY_TIMEOUT';
  message: string;
}

Usage Examples:

import { request, errors } from 'undici';

try {
  const response = await request('https://slow-server.example.com/data', {
    headersTimeout: 5000,  // 5 second headers timeout
    bodyTimeout: 10000     // 10 second body timeout
  });
} catch (error) {
  if (error instanceof errors.ConnectTimeoutError) {
    console.log('Failed to establish connection within timeout');
  } else if (error instanceof errors.HeadersTimeoutError) {
    console.log('Headers not received within timeout');
  } else if (error instanceof errors.BodyTimeoutError) {
    console.log('Response body not received within timeout');
  }
}

HTTP Response Errors

Errors related to HTTP response processing and status codes.

/**
 * General HTTP response error
 */
class ResponseError extends UndiciError {
  name: 'ResponseError';
  code: 'UND_ERR_RESPONSE';
  statusCode: number;
  headers: Record<string, string | string[]>;
  body: any;
}

/**
 * HTTP status code error
 */
class ResponseStatusCodeError extends UndiciError {
  name: 'ResponseStatusCodeError';
  code: 'UND_ERR_RESPONSE_STATUS_CODE';
  statusCode: number;
  headers: Record<string, string | string[]>;
  body: any;
}

/**
 * Request retry error after exhausting retries
 */
class RequestRetryError extends UndiciError {
  name: 'RequestRetryError';
  code: 'UND_ERR_REQ_RETRY';
  statusCode?: number;
  data: {
    count: number;
    error: Error;
  };
}

Usage Examples:

import { request, errors } from 'undici';

try {
  const response = await request('https://api.example.com/protected', {
    method: 'GET',
    throwOnError: true
  });
} catch (error) {
  if (error instanceof errors.ResponseStatusCodeError) {
    console.log(`HTTP ${error.statusCode}: ${error.message}`);
    console.log('Response headers:', error.headers);
    console.log('Response body:', error.body);
    
    // Handle specific status codes
    if (error.statusCode === 401) {
      console.log('Authentication required');
    } else if (error.statusCode === 403) {
      console.log('Access forbidden');
    } else if (error.statusCode >= 500) {
      console.log('Server error occurred');
    }
  } else if (error instanceof errors.RequestRetryError) {
    console.log(`Request failed after ${error.data.count} retries`);
    console.log('Original error:', error.data.error.message);
  }
}

Client State Errors

Errors related to client lifecycle and state management.

/**
 * Using destroyed client
 */
class ClientDestroyedError extends UndiciError {
  name: 'ClientDestroyedError';
  code: 'UND_ERR_DESTROYED';
  message: 'The client is destroyed';
}

/**
 * Using closed client
 */
class ClientClosedError extends UndiciError {
  name: 'ClientClosedError';
  code: 'UND_ERR_CLOSED';
  message: 'The client is closed';
}

/**
 * Request aborted by user
 */
class RequestAbortedError extends UndiciError {
  name: 'RequestAbortedError';
  code: 'UND_ERR_ABORTED';
  message: 'Request aborted';
}

Usage Examples:

import { Client, errors } from 'undici';

const client = new Client('https://api.example.com');

// Destroy client
await client.destroy();

try {
  // This will throw ClientDestroyedError
  await client.request({ path: '/data' });
} catch (error) {
  if (error instanceof errors.ClientDestroyedError) {
    console.log('Cannot use destroyed client');
  }
}

// Abort request example
const controller = new AbortController();

// Abort after 2 seconds
setTimeout(() => controller.abort(), 2000);

try {
  const response = await client.request({
    path: '/slow-endpoint',
    signal: controller.signal
  });
} catch (error) {
  if (error instanceof errors.RequestAbortedError) {
    console.log('Request was aborted');
  }
}

Protocol and Parsing Errors

Errors related to HTTP protocol violations and parsing failures.

/**
 * Low-level socket errors
 */
class SocketError extends UndiciError {
  name: 'SocketError';
  code: 'UND_ERR_SOCKET';
  socket: {
    localAddress: string;
    localPort: number;
    remoteAddress: string;
    remotePort: number;
  };
}

/**
 * Headers size exceeds limits
 */
class HeadersOverflowError extends UndiciError {
  name: 'HeadersOverflowError';
  code: 'UND_ERR_HEADERS_OVERFLOW';
  message: 'Headers overflow';
}

/**
 * Request body length mismatch
 */
class RequestContentLengthMismatchError extends UndiciError {
  name: 'RequestContentLengthMismatchError';
  code: 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH';
  message: 'Request body length does not match content-length header';
}

/**
 * Response body length mismatch
 */
class ResponseContentLengthMismatchError extends UndiciError {
  name: 'ResponseContentLengthMismatchError';
  code: 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH';
  message: 'Response body length does not match content-length header';
}

/**
 * HTTP parsing errors
 */
class HTTPParserError extends Error {
  name: 'HTTPParserError';
  code: string;
  bytesParsed: number;
}

Usage Examples:

import { request, errors } from 'undici';

try {
  const response = await request('https://malformed-server.example.com/data');
} catch (error) {
  if (error instanceof errors.HTTPParserError) {
    console.log('HTTP parsing failed:', error.message);
    console.log('Bytes parsed:', error.bytesParsed);
  } else if (error instanceof errors.HeadersOverflowError) {
    console.log('Response headers too large');
  } else if (error instanceof errors.ResponseContentLengthMismatchError) {
    console.log('Response body length mismatch');
  } else if (error instanceof errors.SocketError) {
    console.log('Socket error:', error.message);
    console.log('Socket info:', error.socket);
  }
}

Validation Errors

Errors related to invalid arguments and return values.

/**
 * Invalid function arguments
 */
class InvalidArgumentError extends UndiciError {
  name: 'InvalidArgumentError';
  code: 'UND_ERR_INVALID_ARG';
  message: string;
}

/**
 * Invalid return values
 */
class InvalidReturnValueError extends UndiciError {
  name: 'InvalidReturnValueError';
  code: 'UND_ERR_INVALID_RETURN_VALUE';
  message: string;
}

/**
 * Unsupported functionality
 */
class NotSupportedError extends UndiciError {
  name: 'NotSupportedError';
  code: 'UND_ERR_NOT_SUPPORTED';
  message: string;
}

Usage Examples:

import { request, errors } from 'undici';

try {
  // Invalid URL will throw InvalidArgumentError
  await request(null);
} catch (error) {
  if (error instanceof errors.InvalidArgumentError) {
    console.log('Invalid argument provided:', error.message);
  }
}

try {
  // Unsupported feature
  await request('https://api.example.com', {
    method: 'INVALID_METHOD'
  });
} catch (error) {
  if (error instanceof errors.NotSupportedError) {
    console.log('Feature not supported:', error.message);
  }
}

Error Handling Patterns

Comprehensive Error Handling

import { request, errors } from 'undici';

async function safeRequest(url, options = {}) {
  try {
    return await request(url, {
      ...options,
      headersTimeout: 10000,
      bodyTimeout: 30000,
      throwOnError: true
    });
  } catch (error) {
    // Network and timeout errors
    if (error instanceof errors.ConnectTimeoutError) {
      throw new Error('Connection timeout - server may be down');
    }
    
    if (error instanceof errors.HeadersTimeoutError) {
      throw new Error('Headers timeout - server may be overloaded');
    }
    
    if (error instanceof errors.BodyTimeoutError) {
      throw new Error('Body timeout - response too large or slow');
    }
    
    // HTTP response errors
    if (error instanceof errors.ResponseStatusCodeError) {
      switch (error.statusCode) {
        case 400:
          throw new Error('Bad request - check your request data');
        case 401:
          throw new Error('Authentication required');
        case 403:
          throw new Error('Access forbidden');
        case 404:
          throw new Error('Resource not found');
        case 429:
          throw new Error('Rate limit exceeded');
        case 500:
          throw new Error('Internal server error');
        case 502:
        case 503:
        case 504:
          throw new Error('Server temporarily unavailable');
        default:
          throw new Error(`HTTP ${error.statusCode}: ${error.message}`);
      }
    }
    
    // Client state errors
    if (error instanceof errors.ClientDestroyedError) {
      throw new Error('HTTP client was destroyed');
    }
    
    if (error instanceof errors.RequestAbortedError) {
      throw new Error('Request was cancelled');
    }
    
    // Protocol errors
    if (error instanceof errors.HTTPParserError) {
      throw new Error('Invalid HTTP response from server');
    }
    
    // Validation errors
    if (error instanceof errors.InvalidArgumentError) {
      throw new Error('Invalid request parameters');
    }
    
    // Unknown error
    throw new Error(`Unexpected error: ${error.message}`);
  }
}

Retry with Error Classification

import { request, errors } from 'undici';

async function requestWithRetry(url, options = {}, maxRetries = 3) {
  let attempt = 0;
  
  while (attempt <= maxRetries) {
    try {
      return await request(url, options);
    } catch (error) {
      attempt++;
      
      // Don't retry client errors (4xx) or validation errors
      if (
        error instanceof errors.ResponseStatusCodeError &&
        error.statusCode >= 400 && error.statusCode < 500
      ) {
        throw error;
      }
      
      if (
        error instanceof errors.InvalidArgumentError ||
        error instanceof errors.NotSupportedError ||
        error instanceof errors.ClientDestroyedError
      ) {
        throw error;
      }
      
      // Retry on network errors, timeouts, and server errors
      const shouldRetry = 
        error instanceof errors.ConnectTimeoutError ||
        error instanceof errors.HeadersTimeoutError ||
        error instanceof errors.BodyTimeoutError ||
        error instanceof errors.SocketError ||
        (error instanceof errors.ResponseStatusCodeError && 
         error.statusCode >= 500);
      
      if (!shouldRetry || attempt > maxRetries) {
        throw error;
      }
      
      // Exponential backoff
      const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Error Logging and Monitoring

import { request, errors } from 'undici';

function logError(error, context = {}) {
  const errorInfo = {
    timestamp: new Date().toISOString(),
    name: error.name,
    code: error.code,
    message: error.message,
    ...context
  };
  
  // Add specific error details
  if (error instanceof errors.ResponseStatusCodeError) {
    errorInfo.statusCode = error.statusCode;
    errorInfo.headers = error.headers;
  }
  
  if (error instanceof errors.RequestRetryError) {
    errorInfo.retryCount = error.data.count;
    errorInfo.originalError = error.data.error.message;
  }
  
  if (error instanceof errors.SocketError && error.socket) {
    errorInfo.socket = error.socket;
  }
  
  console.error('HTTP Request Error:', JSON.stringify(errorInfo, null, 2));
  
  // Send to monitoring service
  // monitoringService.logError(errorInfo);
}

async function monitoredRequest(url, options = {}) {
  try {
    return await request(url, options);
  } catch (error) {
    logError(error, { url, method: options.method || 'GET' });
    throw error;
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-undici

docs

caching.md

connection-management.md

cookies.md

core-http.md

errors.md

global-config.md

headers-body.md

index.md

interceptors.md

mock-testing.md

web-standards.md

tile.json