or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdcore-operations.mderror-handling.mdindex.mdinterceptors.mdutilities.md

error-handling.mddocs/

0

# Error Handling and Retry Logic

1

2

Enhanced error objects with request/response context, automatic retry for specific status codes, configurable retry strategies, and comprehensive error information for debugging and monitoring.

3

4

## Capabilities

5

6

### FetchError Class

7

8

Enhanced error class that provides comprehensive information about fetch failures including request details, response data, and contextual information.

9

10

```typescript { .api }

11

/**

12

* Enhanced error class for fetch failures

13

* Provides comprehensive context about the failed request

14

*/

15

class FetchError<T = any> extends Error implements IFetchError<T> {

16

name: "FetchError";

17

request?: FetchRequest;

18

options?: FetchOptions;

19

response?: FetchResponse<T>;

20

data?: T;

21

status?: number;

22

statusText?: string;

23

statusCode?: number; // Alias for status

24

statusMessage?: string; // Alias for statusText

25

cause?: unknown;

26

}

27

28

interface IFetchError<T = any> extends Error {

29

request?: FetchRequest;

30

options?: FetchOptions;

31

response?: FetchResponse<T>;

32

data?: T;

33

status?: number;

34

statusText?: string;

35

statusCode?: number;

36

statusMessage?: string;

37

}

38

```

39

40

**Usage Examples:**

41

42

```typescript

43

import { ofetch, FetchError } from "ofetch";

44

45

try {

46

const data = await ofetch("https://api.example.com/users/999");

47

} catch (error) {

48

if (error instanceof FetchError) {

49

console.log("Request:", error.request);

50

console.log("Status:", error.status); // 404

51

console.log("Status Text:", error.statusText); // "Not Found"

52

console.log("Response Data:", error.data); // Parsed error response

53

console.log("Original Options:", error.options);

54

55

// Handle specific error types

56

if (error.status === 404) {

57

console.log("Resource not found");

58

} else if (error.status >= 500) {

59

console.log("Server error");

60

}

61

}

62

}

63

64

// Access error data for API error messages

65

try {

66

await ofetch("/api/users", { method: "POST", body: {} });

67

} catch (error) {

68

if (error instanceof FetchError && error.data) {

69

console.log("API Error:", error.data.message);

70

console.log("Validation Errors:", error.data.errors);

71

}

72

}

73

```

74

75

### Error Creation Factory

76

77

Factory function for creating FetchError instances with proper context population.

78

79

```typescript { .api }

80

/**

81

* Factory function to create FetchError instances

82

* @param ctx - Fetch context containing request, response, and error details

83

* @returns Enhanced FetchError with populated context

84

*/

85

function createFetchError<T = any>(ctx: FetchContext<T>): IFetchError<T>;

86

```

87

88

### Automatic Retry Logic

89

90

ofetch automatically retries requests for specific HTTP status codes with configurable retry counts and delays.

91

92

```typescript { .api }

93

interface FetchOptions {

94

/** Number of retry attempts, or false to disable retry */

95

retry?: number | false;

96

/** Delay between retries in milliseconds, or function for custom delay calculation */

97

retryDelay?: number | ((context: FetchContext<T, R>) => number);

98

/** HTTP status codes that trigger retries (default: [408, 409, 425, 429, 500, 502, 503, 504]) */

99

retryStatusCodes?: number[];

100

}

101

```

102

103

**Default Retry Status Codes:**

104

- `408` - Request Timeout

105

- `409` - Conflict

106

- `425` - Too Early (Experimental)

107

- `429` - Too Many Requests

108

- `500` - Internal Server Error

109

- `502` - Bad Gateway

110

- `503` - Service Unavailable

111

- `504` - Gateway Timeout

112

113

**Usage Examples:**

114

115

```typescript

116

import { ofetch } from "ofetch";

117

118

// Default retry behavior (1 retry for GET, 0 for POST/PUT/PATCH/DELETE)

119

const data = await ofetch("https://unreliable-api.com/data");

120

121

// Custom retry configuration

122

const data = await ofetch("https://api.example.com/data", {

123

retry: 3,

124

retryDelay: 1000, // 1 second between retries

125

retryStatusCodes: [408, 429, 500, 502, 503] // Custom retry codes

126

});

127

128

// Exponential backoff retry delay

129

const data = await ofetch("https://api.example.com/data", {

130

retry: 5,

131

retryDelay: (context) => {

132

const attempt = (context.options.retry || 0) - ((context.options as any)._retryCount || 0);

133

return Math.min(1000 * Math.pow(2, attempt), 30000); // Max 30 seconds

134

}

135

});

136

137

// Disable retry for specific request

138

const data = await ofetch("https://api.example.com/data", {

139

retry: false

140

});

141

142

// Force retry for POST requests (normally no retry)

143

const result = await ofetch("https://api.example.com/submit", {

144

method: "POST",

145

body: { data: "value" },

146

retry: 2 // Override default no-retry for POST

147

});

148

```

149

150

### Error Response Handling

151

152

Control whether ofetch throws errors for HTTP error status codes.

153

154

```typescript { .api }

155

interface FetchOptions {

156

/** Skip throwing errors for 4xx/5xx status codes */

157

ignoreResponseError?: boolean;

158

}

159

```

160

161

**Usage Examples:**

162

163

```typescript

164

import { ofetch } from "ofetch";

165

166

// Handle errors manually without throwing

167

const response = await ofetch.raw("https://api.example.com/might-fail", {

168

ignoreResponseError: true

169

});

170

171

if (response.status >= 400) {

172

console.log("Error:", response.status, response._data);

173

} else {

174

console.log("Success:", response._data);

175

}

176

177

// Conditional error handling

178

const data = await ofetch("https://api.example.com/users/999", {

179

ignoreResponseError: true

180

});

181

182

// For regular ofetch calls with ignoreResponseError, check manually

183

try {

184

const data = await ofetch("https://api.example.com/data", {

185

ignoreResponseError: true

186

});

187

// No error thrown, but data might be error response

188

if (data && data.error) {

189

console.log("API returned error:", data.error);

190

}

191

} catch (error) {

192

// Network or other non-HTTP errors still throw

193

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

194

}

195

```

196

197

### Error Context and Debugging

198

199

Comprehensive error context for debugging and monitoring.

200

201

**Usage Examples:**

202

203

```typescript

204

import { ofetch, FetchError } from "ofetch";

205

206

// Comprehensive error logging

207

try {

208

const data = await ofetch("https://api.example.com/data", {

209

method: "POST",

210

body: { invalid: "data" },

211

headers: { "Content-Type": "application/json" }

212

});

213

} catch (error) {

214

if (error instanceof FetchError) {

215

// Log full error context

216

console.error("Fetch Error Details:", {

217

message: error.message,

218

url: error.request,

219

method: error.options?.method,

220

status: error.status,

221

statusText: error.statusText,

222

requestHeaders: error.options?.headers,

223

responseHeaders: error.response?.headers,

224

requestBody: error.options?.body,

225

responseBody: error.data,

226

timestamp: new Date().toISOString()

227

});

228

}

229

}

230

231

// Error categorization for monitoring

232

function categorizeError(error: FetchError) {

233

if (!error.status) return "network_error";

234

if (error.status >= 400 && error.status < 500) return "client_error";

235

if (error.status >= 500) return "server_error";

236

return "unknown_error";

237

}

238

239

try {

240

await ofetch("https://api.example.com/data");

241

} catch (error) {

242

if (error instanceof FetchError) {

243

const category = categorizeError(error);

244

metrics.increment(`http_error.${category}`, {

245

status: error.status,

246

endpoint: error.request

247

});

248

}

249

}

250

```

251

252

### Timeout Handling

253

254

Configure request timeouts with automatic AbortController integration.

255

256

```typescript { .api }

257

interface FetchOptions {

258

/** Timeout in milliseconds */

259

timeout?: number;

260

}

261

```

262

263

**Usage Examples:**

264

265

```typescript

266

import { ofetch } from "ofetch";

267

268

// Request timeout

269

try {

270

const data = await ofetch("https://slow-api.com/data", {

271

timeout: 5000 // 5 second timeout

272

});

273

} catch (error) {

274

if (error.name === "TimeoutError") {

275

console.log("Request timed out after 5 seconds");

276

}

277

}

278

279

// Different timeouts for different operations

280

const api = ofetch.create({

281

baseURL: "https://api.example.com",

282

timeout: 10000 // Default 10 second timeout

283

});

284

285

// Override timeout for specific requests

286

const quickData = await api("/quick-endpoint", { timeout: 2000 });

287

const slowData = await api("/slow-endpoint", { timeout: 30000 });

288

```

289

290

### Error Handling Patterns

291

292

Common patterns for handling different types of errors in applications.

293

294

**Usage Examples:**

295

296

```typescript

297

import { ofetch, FetchError } from "ofetch";

298

299

// Wrapper function with comprehensive error handling

300

async function apiRequest<T>(url: string, options?: any): Promise<T> {

301

try {

302

return await ofetch<T>(url, options);

303

} catch (error) {

304

if (error instanceof FetchError) {

305

// Handle specific HTTP errors

306

switch (error.status) {

307

case 401:

308

throw new Error("Authentication required");

309

case 403:

310

throw new Error("Access denied");

311

case 404:

312

throw new Error("Resource not found");

313

case 429:

314

throw new Error("Rate limit exceeded");

315

case 500:

316

throw new Error("Server error - please try again later");

317

default:

318

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

319

}

320

}

321

322

// Handle network errors

323

if (error.name === "TimeoutError") {

324

throw new Error("Request timeout - please check your connection");

325

}

326

327

throw error; // Re-throw unknown errors

328

}

329

}

330

331

// Retry with custom error handling

332

async function retryableRequest<T>(url: string, maxRetries = 3): Promise<T> {

333

let lastError: Error;

334

335

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

336

try {

337

return await ofetch<T>(url, {

338

timeout: 5000 * attempt, // Increasing timeout

339

retry: false // Handle retry manually

340

});

341

} catch (error) {

342

lastError = error;

343

344

if (error instanceof FetchError) {

345

// Don't retry client errors (4xx)

346

if (error.status && error.status >= 400 && error.status < 500) {

347

throw error;

348

}

349

}

350

351

if (attempt < maxRetries) {

352

await new Promise(resolve => setTimeout(resolve, 1000 * attempt));

353

}

354

}

355

}

356

357

throw lastError!;

358

}

359

360

// Global error handler with logging

361

const api = ofetch.create({

362

baseURL: "https://api.example.com",

363

onResponseError({ request, response, error }) {

364

// Log all API errors

365

logger.error("API Error", {

366

url: request,

367

status: response.status,

368

data: response._data,

369

timestamp: new Date().toISOString()

370

});

371

}

372

});

373

```