or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

body-processing.mderror-handling.mdfile-blob.mdheaders.mdhttp-client.mdindex.mdrequest-response.mdutilities.md

error-handling.mddocs/

0

# Error Handling

1

2

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

3

4

## Capabilities

5

6

### FetchError Class

7

8

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

9

10

```javascript { .api }

11

/**

12

* Error class for fetch operation failures

13

*/

14

class FetchError extends Error {

15

constructor(message: string, type: string, systemError?: Record<string, unknown>);

16

17

readonly name: 'FetchError';

18

readonly type: string;

19

readonly code?: string;

20

readonly errno?: string;

21

readonly erroredSysCall?: string;

22

}

23

```

24

25

**Common Error Types:**

26

27

- `'system'` - Network-level errors (DNS resolution, connection failures)

28

- `'max-redirect'` - Too many redirects encountered

29

- `'max-size'` - Response body exceeded size limit

30

- `'invalid-redirect'` - Invalid redirect URL

31

- `'no-redirect'` - Redirect encountered when redirect: 'error'

32

- `'unsupported-redirect'` - Cannot redirect with non-replayable body

33

34

**Usage Examples:**

35

36

```javascript

37

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

38

39

try {

40

const response = await fetch('https://nonexistent-domain.example');

41

} catch (error) {

42

if (error instanceof FetchError) {

43

console.log('Fetch error type:', error.type);

44

console.log('Error message:', error.message);

45

46

switch (error.type) {

47

case 'system':

48

console.log('Network error:', error.code);

49

if (error.code === 'ENOTFOUND') {

50

console.log('Domain not found');

51

} else if (error.code === 'ECONNREFUSED') {

52

console.log('Connection refused');

53

}

54

break;

55

56

case 'max-redirect':

57

console.log('Too many redirects');

58

break;

59

60

case 'max-size':

61

console.log('Response too large');

62

break;

63

}

64

}

65

}

66

```

67

68

### AbortError Class

69

70

Error class specifically for cancelled/aborted requests using AbortSignal.

71

72

```javascript { .api }

73

/**

74

* Error class for aborted requests

75

*/

76

class AbortError extends Error {

77

constructor(message: string, type?: string);

78

79

readonly name: 'AbortError';

80

readonly type: string;

81

}

82

```

83

84

**Usage Examples:**

85

86

```javascript

87

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

88

89

// Request cancellation with timeout

90

const controller = new AbortController();

91

const timeoutId = setTimeout(() => controller.abort(), 5000);

92

93

try {

94

const response = await fetch('https://api.example.com/slow-endpoint', {

95

signal: controller.signal

96

});

97

98

clearTimeout(timeoutId);

99

const data = await response.json();

100

101

} catch (error) {

102

if (error instanceof AbortError) {

103

console.log('Request was cancelled:', error.message);

104

console.log('Error type:', error.type); // 'aborted'

105

} else {

106

console.log('Other error:', error.message);

107

}

108

}

109

```

110

111

### System Error Details

112

113

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

114

115

```javascript { .api }

116

interface SystemErrorDetails {

117

code?: string; // System error code (ENOTFOUND, ECONNREFUSED, etc.)

118

errno?: string; // System error number

119

erroredSysCall?: string; // System call that failed

120

}

121

```

122

123

**Usage Examples:**

124

125

```javascript

126

try {

127

await fetch('https://192.168.1.999:8080/api'); // Invalid IP

128

} catch (error) {

129

if (error instanceof FetchError && error.type === 'system') {

130

console.log('System error code:', error.code);

131

console.log('System call:', error.erroredSysCall);

132

console.log('Error number:', error.errno);

133

134

// Handle specific system errors

135

switch (error.code) {

136

case 'ENOTFOUND':

137

console.log('DNS lookup failed - domain not found');

138

break;

139

case 'ECONNREFUSED':

140

console.log('Connection refused - server not accepting connections');

141

break;

142

case 'ETIMEDOUT':

143

console.log('Connection timed out');

144

break;

145

case 'ECONNRESET':

146

console.log('Connection reset by peer');

147

break;

148

default:

149

console.log('Unknown system error:', error.code);

150

}

151

}

152

}

153

```

154

155

### Error Handling Patterns

156

157

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

158

159

```javascript { .api }

160

interface ErrorHandlingPattern {

161

handleNetworkError(error: FetchError): void;

162

handleHttpError(response: Response): Promise<void>;

163

retryWithBackoff(url: string, options?: RequestInit, maxRetries?: number): Promise<Response>;

164

}

165

```

166

167

**Network Error Handling:**

168

169

```javascript

170

async function robustFetch(url, options = {}, retries = 3) {

171

for (let attempt = 1; attempt <= retries; attempt++) {

172

try {

173

return await fetch(url, options);

174

} catch (error) {

175

if (error instanceof FetchError) {

176

console.log(`Attempt ${attempt} failed:`, error.type);

177

178

// Don't retry certain error types

179

if (error.type === 'max-size' || error.type === 'invalid-redirect') {

180

throw error;

181

}

182

183

// Only retry system errors (network issues)

184

if (error.type !== 'system' || attempt === retries) {

185

throw error;

186

}

187

188

// Wait before retry with exponential backoff

189

const delay = Math.pow(2, attempt - 1) * 1000;

190

await new Promise(resolve => setTimeout(resolve, delay));

191

192

} else if (error instanceof AbortError) {

193

// Don't retry aborted requests

194

throw error;

195

} else {

196

throw error;

197

}

198

}

199

}

200

}

201

```

202

203

**HTTP Error Handling:**

204

205

```javascript

206

async function fetchWithErrorHandling(url, options = {}) {

207

let response;

208

209

try {

210

response = await fetch(url, options);

211

} catch (error) {

212

if (error instanceof FetchError) {

213

throw new Error(`Network error: ${error.message}`);

214

} else if (error instanceof AbortError) {

215

throw new Error('Request was cancelled');

216

} else {

217

throw error;

218

}

219

}

220

221

// Handle HTTP error status codes

222

if (!response.ok) {

223

let errorMessage = `HTTP ${response.status}: ${response.statusText}`;

224

225

try {

226

// Try to get error details from response body

227

const contentType = response.headers.get('content-type');

228

229

if (contentType?.includes('application/json')) {

230

const errorBody = await response.json();

231

errorMessage += ` - ${errorBody.message || errorBody.error || 'Unknown error'}`;

232

} else if (contentType?.includes('text/')) {

233

const errorText = await response.text();

234

if (errorText.length < 200) { // Avoid logging huge HTML error pages

235

errorMessage += ` - ${errorText}`;

236

}

237

}

238

} catch (bodyError) {

239

// Ignore errors reading response body

240

}

241

242

throw new Error(errorMessage);

243

}

244

245

return response;

246

}

247

```

248

249

### Timeout Handling

250

251

Implementing request timeouts using AbortController and handling timeout errors.

252

253

```javascript { .api }

254

interface TimeoutConfig {

255

timeout: number;

256

signal?: AbortSignal;

257

}

258

```

259

260

**Usage Examples:**

261

262

```javascript

263

// Simple timeout wrapper

264

function fetchWithTimeout(url, options = {}, timeout = 5000) {

265

const controller = new AbortController();

266

const id = setTimeout(() => controller.abort(), timeout);

267

268

return fetch(url, {

269

...options,

270

signal: controller.signal

271

}).finally(() => clearTimeout(id));

272

}

273

274

// Advanced timeout with custom error

275

class TimeoutError extends Error {

276

constructor(timeout) {

277

super(`Request timed out after ${timeout}ms`);

278

this.name = 'TimeoutError';

279

this.timeout = timeout;

280

}

281

}

282

283

async function fetchWithCustomTimeout(url, options = {}, timeout = 10000) {

284

const controller = new AbortController();

285

const timeoutId = setTimeout(() => controller.abort(), timeout);

286

287

try {

288

const response = await fetch(url, {

289

...options,

290

signal: controller.signal

291

});

292

293

clearTimeout(timeoutId);

294

return response;

295

296

} catch (error) {

297

clearTimeout(timeoutId);

298

299

if (error instanceof AbortError) {

300

throw new TimeoutError(timeout);

301

}

302

throw error;

303

}

304

}

305

306

// Usage with error handling

307

try {

308

const response = await fetchWithCustomTimeout(

309

'https://api.example.com/slow-endpoint',

310

{ method: 'GET' },

311

3000

312

);

313

const data = await response.json();

314

} catch (error) {

315

if (error instanceof TimeoutError) {

316

console.log('Request timed out:', error.timeout);

317

} else if (error instanceof FetchError) {

318

console.log('Network error:', error.message);

319

} else {

320

console.log('Other error:', error.message);

321

}

322

}

323

```

324

325

### Redirect Error Handling

326

327

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

328

329

```javascript { .api }

330

interface RedirectErrorHandling {

331

handleRedirectError(error: FetchError): void;

332

followRedirectsManually(url: string, options?: RequestInit): Promise<Response>;

333

}

334

```

335

336

**Usage Examples:**

337

338

```javascript

339

// Handle redirect limits

340

try {

341

const response = await fetch('https://example.com/redirect-chain', {

342

follow: 3 // Limit to 3 redirects

343

});

344

} catch (error) {

345

if (error instanceof FetchError && error.type === 'max-redirect') {

346

console.log('Too many redirects encountered');

347

// Could implement manual redirect handling here

348

}

349

}

350

351

// Manual redirect handling

352

async function followRedirectsManually(url, options = {}, maxRedirects = 5) {

353

let currentUrl = url;

354

let redirectCount = 0;

355

356

while (redirectCount < maxRedirects) {

357

const response = await fetch(currentUrl, {

358

...options,

359

redirect: 'manual'

360

});

361

362

if (response.status >= 300 && response.status < 400) {

363

const location = response.headers.get('location');

364

if (!location) {

365

throw new Error('Redirect response missing Location header');

366

}

367

368

currentUrl = new URL(location, currentUrl).toString();

369

redirectCount++;

370

console.log(`Redirect ${redirectCount}: ${currentUrl}`);

371

372

} else {

373

return response;

374

}

375

}

376

377

throw new Error(`Too many redirects (${maxRedirects})`);

378

}

379

```

380

381

### Error Recovery Strategies

382

383

Comprehensive error recovery patterns for robust HTTP clients.

384

385

```javascript { .api }

386

interface ErrorRecoveryStrategy {

387

exponentialBackoff(attempt: number): number;

388

shouldRetry(error: Error): boolean;

389

circuitBreaker(url: string): boolean;

390

}

391

```

392

393

**Usage Examples:**

394

395

```javascript

396

// Circuit breaker pattern

397

class CircuitBreaker {

398

constructor(threshold = 5, timeout = 60000) {

399

this.failures = new Map();

400

this.threshold = threshold;

401

this.timeout = timeout;

402

}

403

404

canExecute(url) {

405

const failure = this.failures.get(url);

406

if (!failure) return true;

407

408

if (failure.count >= this.threshold) {

409

if (Date.now() - failure.lastFailure < this.timeout) {

410

return false; // Circuit open

411

} else {

412

// Try again after timeout

413

this.failures.delete(url);

414

return true;

415

}

416

}

417

return true;

418

}

419

420

recordSuccess(url) {

421

this.failures.delete(url);

422

}

423

424

recordFailure(url) {

425

const failure = this.failures.get(url) || { count: 0, lastFailure: 0 };

426

failure.count++;

427

failure.lastFailure = Date.now();

428

this.failures.set(url, failure);

429

}

430

}

431

432

// Comprehensive error handling with circuit breaker

433

const circuitBreaker = new CircuitBreaker();

434

435

async function resilientFetch(url, options = {}, maxRetries = 3) {

436

if (!circuitBreaker.canExecute(url)) {

437

throw new Error('Circuit breaker is open for this URL');

438

}

439

440

for (let attempt = 1; attempt <= maxRetries; attempt++) {

441

try {

442

const response = await fetchWithTimeout(url, options, 10000);

443

444

if (!response.ok) {

445

throw new Error(`HTTP ${response.status}: ${response.statusText}`);

446

}

447

448

circuitBreaker.recordSuccess(url);

449

return response;

450

451

} catch (error) {

452

console.log(`Attempt ${attempt} failed:`, error.message);

453

454

// Don't retry certain errors

455

if (error instanceof AbortError ||

456

(error instanceof FetchError && error.type === 'max-size')) {

457

circuitBreaker.recordFailure(url);

458

throw error;

459

}

460

461

if (attempt === maxRetries) {

462

circuitBreaker.recordFailure(url);

463

throw error;

464

}

465

466

// Exponential backoff

467

const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);

468

await new Promise(resolve => setTimeout(resolve, delay));

469

}

470

}

471

}

472

```