or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mderrors.mdhooks.mdhttp-methods.mdindex.mdinstances.mdresponses.mdretry.md
tile.json

errors.mddocs/

0

# Error Handling

1

2

Comprehensive error handling with detailed HTTP and timeout error classes containing request and response information. Ky automatically throws errors for non-2xx responses and provides detailed error objects.

3

4

## Capabilities

5

6

### HTTPError Class

7

8

Thrown for HTTP responses with non-2xx status codes (when `throwHttpErrors` is true).

9

10

```typescript { .api }

11

/**

12

* HTTP Error class for non-2xx responses

13

* Contains detailed information about the failed request and response

14

*/

15

class HTTPError<T = unknown> extends Error {

16

/** The HTTP response that caused the error */

17

response: KyResponse<T>;

18

/** The original request */

19

request: KyRequest;

20

/** The normalized options used for the request */

21

options: NormalizedOptions;

22

/** Error name */

23

name: "HTTPError";

24

/** Descriptive error message with status and URL */

25

message: string;

26

}

27

```

28

29

**Usage Examples:**

30

31

```typescript

32

import ky, { HTTPError } from "ky";

33

34

try {

35

const data = await ky.get("https://api.example.com/not-found").json();

36

} catch (error) {

37

if (error instanceof HTTPError) {

38

console.log("HTTP Error Details:");

39

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

40

console.log("Status Text:", error.response.statusText);

41

console.log("URL:", error.request.url);

42

console.log("Method:", error.request.method);

43

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

44

45

// Access response body for more details

46

const errorBody = await error.response.text();

47

console.log("Response Body:", errorBody);

48

49

// Check specific status codes

50

if (error.response.status === 404) {

51

console.log("Resource not found");

52

} else if (error.response.status === 401) {

53

console.log("Authentication required");

54

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

55

console.log("Server error");

56

}

57

}

58

}

59

60

// Handle different error types

61

const handleApiCall = async () => {

62

try {

63

return await ky.post("https://api.example.com/data", {

64

json: { value: "test" }

65

}).json();

66

} catch (error) {

67

if (error instanceof HTTPError) {

68

// HTTP errors (4xx, 5xx responses)

69

switch (error.response.status) {

70

case 400:

71

throw new Error("Invalid request data");

72

case 401:

73

throw new Error("Please log in to continue");

74

case 403:

75

throw new Error("You don't have permission for this action");

76

case 404:

77

throw new Error("The requested resource was not found");

78

case 429:

79

throw new Error("Rate limit exceeded. Please try again later");

80

case 500:

81

throw new Error("Server error. Please try again later");

82

default:

83

throw new Error(`Request failed with status ${error.response.status}`);

84

}

85

} else {

86

// Network errors, timeouts, etc.

87

throw new Error("Network error. Please check your connection");

88

}

89

}

90

};

91

```

92

93

### TimeoutError Class

94

95

Thrown when requests exceed the configured timeout duration.

96

97

```typescript { .api }

98

/**

99

* Timeout Error class for requests that exceed timeout duration

100

* Contains information about the request that timed out

101

*/

102

class TimeoutError extends Error {

103

/** The original request that timed out */

104

request: KyRequest;

105

/** Error name */

106

name: "TimeoutError";

107

/** Descriptive error message with method and URL */

108

message: string;

109

}

110

```

111

112

**Usage Examples:**

113

114

```typescript

115

import ky, { TimeoutError } from "ky";

116

117

try {

118

const data = await ky.get("https://api.example.com/slow-endpoint", {

119

timeout: 5000 // 5 second timeout

120

}).json();

121

} catch (error) {

122

if (error instanceof TimeoutError) {

123

console.log("Request timed out:");

124

console.log("URL:", error.request.url);

125

console.log("Method:", error.request.method);

126

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

127

128

// Handle timeout specifically

129

throw new Error("The request took too long. Please try again");

130

}

131

}

132

133

// Timeout handling with retry logic

134

const fetchWithTimeoutHandling = async (url: string, maxAttempts = 3) => {

135

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

136

try {

137

return await ky.get(url, {

138

timeout: 10000 // 10 seconds

139

}).json();

140

} catch (error) {

141

if (error instanceof TimeoutError) {

142

console.log(`Attempt ${attempt} timed out`);

143

144

if (attempt === maxAttempts) {

145

throw new Error("Request failed: Multiple timeout attempts");

146

}

147

148

// Wait before retrying

149

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

150

continue;

151

}

152

153

// Re-throw non-timeout errors immediately

154

throw error;

155

}

156

}

157

};

158

```

159

160

### Error Handling Patterns

161

162

Common patterns for handling different types of errors in applications.

163

164

**Usage Examples:**

165

166

```typescript

167

import ky, { HTTPError, TimeoutError } from "ky";

168

169

// Comprehensive error handling function

170

const safeApiCall = async <T>(

171

url: string,

172

options?: any

173

): Promise<{ data?: T; error?: string }> => {

174

try {

175

const data = await ky(url, options).json<T>();

176

return { data };

177

} catch (error) {

178

if (error instanceof HTTPError) {

179

// Handle HTTP errors

180

const status = error.response.status;

181

182

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

183

// Client errors

184

return { error: `Client error: ${status}` };

185

} else if (status >= 500) {

186

// Server errors

187

return { error: "Server error. Please try again later" };

188

}

189

} else if (error instanceof TimeoutError) {

190

// Handle timeouts

191

return { error: "Request timeout. Please try again" };

192

} else {

193

// Handle other errors (network, etc.)

194

return { error: "Network error. Please check your connection" };

195

}

196

197

return { error: "An unexpected error occurred" };

198

}

199

};

200

201

// Usage of safe API call

202

const loadUserData = async (userId: string) => {

203

const { data, error } = await safeApiCall<User>(`/api/users/${userId}`);

204

205

if (error) {

206

showErrorMessage(error);

207

return null;

208

}

209

210

return data;

211

};

212

213

// Error boundary for React-like patterns

214

const withErrorHandling = <T extends any[], R>(

215

fn: (...args: T) => Promise<R>

216

) => {

217

return async (...args: T): Promise<R | null> => {

218

try {

219

return await fn(...args);

220

} catch (error) {

221

if (error instanceof HTTPError) {

222

// Log HTTP errors with context

223

console.error("HTTP Error:", {

224

url: error.request.url,

225

method: error.request.method,

226

status: error.response.status,

227

statusText: error.response.statusText

228

});

229

230

// Get response body for debugging

231

try {

232

const body = await error.response.clone().text();

233

console.error("Response body:", body);

234

} catch {

235

// Ignore if body can't be read

236

}

237

} else if (error instanceof TimeoutError) {

238

console.error("Timeout Error:", {

239

url: error.request.url,

240

method: error.request.method

241

});

242

} else {

243

console.error("Unexpected error:", error);

244

}

245

246

return null;

247

}

248

};

249

};

250

251

// Wrapped API functions

252

const getUser = withErrorHandling(async (id: string) => {

253

return ky.get(`/api/users/${id}`).json<User>();

254

});

255

256

const createUser = withErrorHandling(async (userData: CreateUserData) => {

257

return ky.post("/api/users", { json: userData }).json<User>();

258

});

259

```

260

261

### Disabling Automatic Error Throwing

262

263

Configure ky to not throw errors for non-2xx responses.

264

265

**Usage Examples:**

266

267

```typescript

268

import ky from "ky";

269

270

// Disable error throwing globally

271

const tolerantClient = ky.create({

272

throwHttpErrors: false

273

});

274

275

// Handle responses manually

276

const response = await tolerantClient.get("https://api.example.com/data");

277

278

if (response.ok) {

279

const data = await response.json();

280

console.log("Success:", data);

281

} else {

282

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

283

284

// Get error details from response

285

const errorText = await response.text();

286

console.log("Error details:", errorText);

287

}

288

289

// Per-request error handling

290

const checkResourceExists = async (url: string): Promise<boolean> => {

291

const response = await ky.get(url, {

292

throwHttpErrors: false

293

});

294

295

return response.ok;

296

};

297

298

// Manual error handling with detailed information

299

const manualErrorHandling = async () => {

300

const response = await ky.post("https://api.example.com/submit", {

301

json: { data: "test" },

302

throwHttpErrors: false

303

});

304

305

if (!response.ok) {

306

// Handle different status codes manually

307

switch (response.status) {

308

case 400:

309

const validationErrors = await response.json();

310

console.log("Validation errors:", validationErrors);

311

break;

312

case 401:

313

console.log("Authentication required");

314

// Redirect to login

315

break;

316

case 403:

317

console.log("Access forbidden");

318

break;

319

case 404:

320

console.log("Resource not found");

321

break;

322

case 429:

323

const retryAfter = response.headers.get("Retry-After");

324

console.log(`Rate limited. Retry after: ${retryAfter}`);

325

break;

326

case 500:

327

console.log("Internal server error");

328

break;

329

default:

330

console.log(`Unexpected error: ${response.status}`);

331

}

332

333

return null;

334

}

335

336

return response.json();

337

};

338

```

339

340

### Custom Error Enhancement

341

342

Use hooks to enhance errors with additional information.

343

344

**Usage Examples:**

345

346

```typescript

347

import ky, { HTTPError } from "ky";

348

349

// Client with enhanced error messages

350

const enhancedClient = ky.create({

351

hooks: {

352

beforeError: [

353

async (error) => {

354

// Add response body to error message

355

try {

356

const responseBody = await error.response.clone().text();

357

if (responseBody) {

358

error.message += `\nResponse: ${responseBody}`;

359

}

360

} catch {

361

// Ignore if response body can't be read

362

}

363

364

// Add request details

365

error.message += `\nRequest: ${error.request.method} ${error.request.url}`;

366

367

// Add headers for debugging

368

const headers = Object.fromEntries(error.request.headers);

369

error.message += `\nHeaders: ${JSON.stringify(headers, null, 2)}`;

370

371

return error;

372

}

373

]

374

}

375

});

376

377

// Custom error class

378

class ApiError extends Error {

379

constructor(

380

message: string,

381

public status: number,

382

public code?: string,

383

public details?: any

384

) {

385

super(message);

386

this.name = "ApiError";

387

}

388

}

389

390

// Client that transforms HTTPError to custom error

391

const customErrorClient = ky.create({

392

hooks: {

393

beforeError: [

394

async (error) => {

395

try {

396

const errorData = await error.response.clone().json();

397

398

// Transform to custom error if response has expected format

399

if (errorData.message || errorData.error) {

400

const customError = new ApiError(

401

errorData.message || errorData.error,

402

error.response.status,

403

errorData.code,

404

errorData.details

405

);

406

407

// Preserve original error properties

408

(customError as any).response = error.response;

409

(customError as any).request = error.request;

410

(customError as any).options = error.options;

411

412

return customError as any;

413

}

414

} catch {

415

// Fall back to original error if transformation fails

416

}

417

418

return error;

419

}

420

]

421

}

422

});

423

424

// Usage with custom error

425

try {

426

await customErrorClient.post("/api/data", {

427

json: { invalid: "data" }

428

}).json();

429

} catch (error) {

430

if (error instanceof ApiError) {

431

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

432

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

433

console.log("Code:", error.code);

434

console.log("Details:", error.details);

435

} else if (error instanceof HTTPError) {

436

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

437

}

438

}

439

```

440

441

## Types

442

443

```typescript { .api }

444

class HTTPError<T = unknown> extends Error {

445

response: KyResponse<T>;

446

request: KyRequest;

447

options: NormalizedOptions;

448

name: "HTTPError";

449

}

450

451

class TimeoutError extends Error {

452

request: KyRequest;

453

name: "TimeoutError";

454

}

455

456

interface KyResponse<T = unknown> extends Response {

457

json<J = T>(): Promise<J>;

458

}

459

460

interface KyRequest<T = unknown> extends Request {

461

json<J = T>(): Promise<J>;

462

}

463

464

interface NormalizedOptions extends RequestInit {

465

method: NonNullable<RequestInit['method']>;

466

credentials?: NonNullable<RequestInit['credentials']>;

467

retry: RetryOptions;

468

prefixUrl: string;

469

onDownloadProgress: Options['onDownloadProgress'];

470

onUploadProgress: Options['onUploadProgress'];

471

}

472

```