Skip to content

Commit aebe6b1

Browse files
author
jarvisjiang
committed
docs: add retry feature documentation and examples
- Add automatic retry feature to README.md and README.cn.md - Create examples/with-retry.ts with 8 comprehensive retry examples - Update CODEBUDDY.md with retry mechanism, stream response type, updated dependency versions, and 12 function overloads - Add retry example link to examples list in both READMEs - Import with-retry.ts in examples/main.ts
1 parent cad1cff commit aebe6b1

File tree

5 files changed

+321
-24
lines changed

5 files changed

+321
-24
lines changed

CODEBUDDY.md

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ This file provides guidance to CodeBuddy Code when working with code in this rep
66

77
**fetchT** is a TypeScript library that wraps the native Fetch API with enhanced capabilities:
88
- Abortable requests via `FetchTask.abort()`
9-
- Type-safe responses with `responseType` parameter ('text' | 'arraybuffer' | 'blob' | 'json')
9+
- Type-safe responses with `responseType` parameter ('text' | 'arraybuffer' | 'blob' | 'json' | 'stream')
1010
- Timeout support
1111
- Progress tracking with streaming
12+
- Automatic retry with configurable strategies
1213
- Rust-like Result type error handling via `happy-rusty` library
1314

1415
Published to both NPM (@happy-ts/fetch-t) and JSR registries with support for Deno, Node, Bun, and browsers.
@@ -86,12 +87,13 @@ Documentation is hosted on GitHub Pages at https://jiangjie.github.io/fetch-t/
8687
│ ├── basic.ts # Basic fetch requests
8788
│ ├── with-progress.ts # Progress tracking examples
8889
│ ├── abortable.ts # Abortable request examples
90+
│ ├── with-retry.ts # Retry strategy examples
8991
│ └── error-handling.ts # Error handling patterns
9092
├── src/ # Source code
9193
│ ├── fetch/
9294
│ │ ├── constants.ts # Error constants (ABORT_ERROR, TIMEOUT_ERROR)
9395
│ │ ├── defines.ts # All type definitions and interfaces
94-
│ │ └── fetch.ts # Core implementation with 10 function overloads
96+
│ │ └── fetch.ts # Core implementation with 12 function overloads
9597
│ └── mod.ts # Public API entry point (re-exports)
9698
├── tests/
9799
│ └── fetch.test.ts # Vitest test suite with MSW mocking
@@ -119,16 +121,16 @@ src/
119121
└── fetch/
120122
├── constants.ts # Error constants (ABORT_ERROR, TIMEOUT_ERROR)
121123
├── defines.ts # All type definitions and interfaces
122-
└── fetch.ts # Core implementation with 10 function overloads
124+
└── fetch.ts # Core implementation with 12 function overloads
123125
```
124126

125127
### Key Design Patterns
126128

127129
1. **Type-Safe Function Overloads**
128-
- The `fetchT` function has 10 distinct overloads to provide compile-time type safety
130+
- The `fetchT` function has 12 distinct overloads to provide compile-time type safety
129131
- Return type varies based on `abortable` and `responseType` parameters
130132
- When `abortable: true`, returns `FetchTask<T>` instead of `FetchResponse<T>`
131-
- Overloads cover all combinations: 4 response types × abortable/non-abortable + fallback overloads
133+
- Overloads cover all combinations: 5 response types × abortable/non-abortable + fallback overloads
132134

133135
2. **Result Monad Pattern**
134136
- Uses `happy-rusty` library's `Result` type for explicit error handling
@@ -144,12 +146,18 @@ src/
144146
- Creates a new Response object with the teed stream to maintain compatibility
145147

146148
4. **Timeout Mechanism**
147-
- Uses `AbortController` + `setTimeout` for timeout implementation
148-
- Timer is automatically cancelled when response completes or fails
149-
- Timeout errors are named `TIMEOUT_ERROR` for easy identification
150-
- `cancelTimer` function ensures cleanup to prevent memory leaks
151-
152-
5. **Custom Error Handling**
149+
- Uses `AbortSignal.timeout()` for timeout implementation (modern browser API)
150+
- Uses `AbortSignal.any()` to combine user abort signal with timeout signal
151+
- Timeout errors are named `TimeoutError` (native DOMException)
152+
153+
5. **Retry Mechanism**
154+
- Configurable via `retry` option (number or `FetchRetryOptions` object)
155+
- Supports static delay or exponential backoff via delay function
156+
- Customizable retry conditions: network errors (default), specific HTTP status codes, or custom function
157+
- `onRetry` callback for logging/metrics before each retry attempt
158+
- User abort stops all retry attempts immediately
159+
160+
6. **Custom Error Handling**
153161
- `FetchError` class extends Error with HTTP status codes
154162
- Constants for common error types: `ABORT_ERROR`, `TIMEOUT_ERROR`
155163
- Non-ok responses (e.g., 404, 500) return `Err(FetchError)` instead of throwing
@@ -166,17 +174,23 @@ src/
166174
- `abortable?: boolean` - Enable abort capability
167175
- `responseType?: FetchResponseType` - Specify return type
168176
- `timeout?: number` - Auto-abort after milliseconds
177+
- `retry?: number | FetchRetryOptions` - Retry configuration
169178
- `onProgress?: (progressResult: IOResult<FetchProgress>) => void` - Track download progress
170179
- `onChunk?: (chunk: Uint8Array) => void` - Receive raw data chunks
180+
- `FetchRetryOptions` - Retry configuration:
181+
- `retries?: number` - Number of retry attempts (default: 0)
182+
- `delay?: number | ((attempt: number) => number)` - Delay between retries
183+
- `when?: number[] | ((error: Error, attempt: number) => boolean)` - Retry conditions
184+
- `onRetry?: (error: Error, attempt: number) => void` - Callback before retry
171185
- `FetchProgress` - Progress tracking with `totalByteLength` and `completedByteLength`
172-
- `FetchResponseType` - Union type: `'text' | 'arraybuffer' | 'blob' | 'json'`
186+
- `FetchResponseType` - Union type: `'text' | 'arraybuffer' | 'blob' | 'json' | 'stream'`
173187
- `FetchResponse<T, E>` - Type alias for `AsyncResult<T, E>` from happy-rusty
174188
- `FetchError` - Custom error class with `status: number` property for HTTP status codes
175189

176190
### Dependencies
177191

178192
**Runtime:**
179-
- `happy-rusty` (^1.6.1) - Provides Result/AsyncResult types for functional error handling
193+
- `happy-rusty` (^1.8.0) - Provides Result/AsyncResult types for functional error handling
180194
- `tiny-invariant` (^1.3.3) - Runtime assertions and validation
181195

182196
**Dev:**
@@ -185,8 +199,8 @@ src/
185199
- `vite-plugin-dts` (^4.5.4) - Bundles TypeScript definitions
186200
- Vitest (^4.0.16) - Test framework
187201
- `@vitest/coverage-v8` (^4.0.16) - Coverage provider
188-
- MSW (^2.12.4) - Mock Service Worker for API mocking in tests
189-
- ESLint (^9.39.2) + typescript-eslint (^8.50.0) - Linting
202+
- MSW (^2.12.7) - Mock Service Worker for API mocking in tests
203+
- ESLint (^9.39.2) + typescript-eslint (^8.51.0) - Linting
190204
- TypeDoc (^0.28.15) - Documentation generation
191205

192206
**External dependencies are marked as external in vite.config.ts** - they are not bundled.
@@ -302,16 +316,16 @@ src/
302316
### Progress Tracking Details
303317
- Requires `Content-Length` header to calculate progress
304318
- If header missing, calls `onProgress(Err(new Error('No content-length...')))` once
305-
- Compatible with both HTTP/1.1 and HTTP/2 (checks both header formats)
319+
- Uses case-insensitive header lookup (per HTTP spec)
306320
- Uses recursive promise chain for reading chunks
307321
- Progress calculation: `completedByteLength += value.byteLength`
308322

309323
### AbortController Behavior
310-
- Shared controller for both timeout and manual abort
311-
- Timeout abort passes custom Error with `name: TIMEOUT_ERROR`
312-
- Manual abort can pass any reason value
324+
- User abort controller created only when `abortable: true`
325+
- Timeout uses native `AbortSignal.timeout()` API
326+
- Multiple signals combined via `AbortSignal.any()`
327+
- Manual abort wraps non-Error reasons in Error with `name: ABORT_ERROR`
313328
- Signal is added to fetch `RequestInit` automatically
314-
- Controller only created when needed (`abortable: true` or `timeout` specified)
315329

316330
## Publishing
317331

@@ -335,7 +349,7 @@ The `prepublishOnly` script automatically runs `pnpm run build`, which includes:
335349

336350
## Known Issues & Gotchas
337351

338-
1. **Progress tracking requires Content-Length header**: If the server doesn't send this header, progress tracking will fail (onProgress receives an Err). The code checks both `content-length` and `Content-Length` for HTTP/2 compatibility.
352+
1. **Progress tracking requires Content-Length header**: If the server doesn't send this header, progress tracking will fail (onProgress receives an Err). The `Headers.get()` method is case-insensitive per the HTTP spec.
339353

340354
2. **Stream tee() limitation**: Progress/chunk callbacks add overhead due to stream splitting. Each chunk is read twice - once for tracking, once for parsing.
341355

@@ -345,11 +359,13 @@ The `prepublishOnly` script automatically runs `pnpm run build`, which includes:
345359

346360
5. **happy-rusty Result API**: Use `isOk()`, `isErr()`, `unwrap()`, `unwrapErr()` methods. Note that `match()` method does NOT exist in happy-rusty.
347361

362+
6. **Retry behavior**: By default, only network errors trigger retries. HTTP errors (4xx, 5xx) require explicit configuration via the `when` option in `FetchRetryOptions`.
363+
348364
## Key Files Reference
349365

350366
### Source Code
351367
- `src/mod.ts` - Main entry point (re-exports from fetch/)
352-
- `src/fetch/fetch.ts` - Core fetchT implementation function with 10 overloads
368+
- `src/fetch/fetch.ts` - Core fetchT implementation function with 12 overloads
353369
- `src/fetch/defines.ts` - All type definitions (FetchTask, FetchInit, FetchError, etc.)
354370
- `src/fetch/constants.ts` - Error constants (ABORT_ERROR, TIMEOUT_ERROR)
355371

@@ -367,6 +383,7 @@ The `prepublishOnly` script automatically runs `pnpm run build`, which includes:
367383
- `examples/basic.ts` - Basic usage examples
368384
- `examples/with-progress.ts` - Progress tracking examples
369385
- `examples/abortable.ts` - Abortable request examples
386+
- `examples/with-retry.ts` - Retry strategy examples
370387
- `examples/error-handling.ts` - Error handling examples
371388

372389
### CI/CD

README.cn.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
[![JSR Version](https://jsr.io/badges/@happy-ts/fetch-t)](https://jsr.io/@happy-ts/fetch-t)
99
[![JSR Score](https://jsr.io/badges/@happy-ts/fetch-t/score)](https://jsr.io/@happy-ts/fetch-t/score)
1010

11-
类型安全的 Fetch API 封装,支持可中止请求、超时、进度追踪和 Rust 风格的 Result 错误处理。
11+
类型安全的 Fetch API 封装,支持可中止请求、超时、进度追踪、自动重试和 Rust 风格的 Result 错误处理。
1212

1313
---
1414

@@ -23,6 +23,7 @@
2323
- **超时支持** - 指定毫秒数后自动中止请求
2424
- **进度追踪** - 通过 `onProgress` 回调监控下载进度
2525
- **数据流处理** - 通过 `onChunk` 回调访问原始数据块
26+
- **自动重试** - 通过 `retry` 选项配置失败重试策略
2627
- **Result 错误处理** - Rust 风格的 `Result` 类型实现显式错误处理
2728
- **跨平台** - 支持 Deno、Node.js、Bun 和浏览器
2829

@@ -80,11 +81,26 @@ setTimeout(() => {
8081
const result = await task.response;
8182
```
8283

84+
### 自动重试
85+
86+
```ts
87+
const result = await fetchT('https://api.example.com/data', {
88+
retry: {
89+
retries: 3,
90+
delay: (attempt) => Math.min(1000 * Math.pow(2, attempt - 1), 10000),
91+
when: [500, 502, 503, 504],
92+
onRetry: (error, attempt) => console.log(`重试 ${attempt}: ${error.message}`),
93+
},
94+
responseType: 'json',
95+
});
96+
```
97+
8398
## 示例
8499

85100
- [基础用法](examples/basic.ts) - 基本请求示例
86101
- [进度追踪](examples/with-progress.ts) - 下载进度和数据流处理
87102
- [可中止请求](examples/abortable.ts) - 取消和超时请求
103+
- [自动重试](examples/with-retry.ts) - 自动重试策略
88104
- [错误处理](examples/error-handling.ts) - 错误处理模式
89105

90106
## 许可证

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
[![JSR Version](https://jsr.io/badges/@happy-ts/fetch-t)](https://jsr.io/@happy-ts/fetch-t)
99
[![JSR Score](https://jsr.io/badges/@happy-ts/fetch-t/score)](https://jsr.io/@happy-ts/fetch-t/score)
1010

11-
Type-safe Fetch API wrapper with abortable requests, timeout support, progress tracking, and Rust-like Result error handling.
11+
Type-safe Fetch API wrapper with abortable requests, timeout support, progress tracking, automatic retry, and Rust-like Result error handling.
1212

1313
---
1414

@@ -23,6 +23,7 @@ Type-safe Fetch API wrapper with abortable requests, timeout support, progress t
2323
- **Timeout Support** - Auto-abort requests after specified milliseconds
2424
- **Progress Tracking** - Monitor download progress with `onProgress` callback
2525
- **Chunk Streaming** - Access raw data chunks via `onChunk` callback
26+
- **Automatic Retry** - Configurable retry strategies with `retry` option
2627
- **Result Error Handling** - Rust-like `Result` type for explicit error handling
2728
- **Cross-platform** - Works with Deno, Node.js, Bun, and browsers
2829

@@ -80,11 +81,26 @@ setTimeout(() => {
8081
const result = await task.response;
8182
```
8283

84+
### Automatic Retry
85+
86+
```ts
87+
const result = await fetchT('https://api.example.com/data', {
88+
retry: {
89+
retries: 3,
90+
delay: (attempt) => Math.min(1000 * Math.pow(2, attempt - 1), 10000),
91+
when: [500, 502, 503, 504],
92+
onRetry: (error, attempt) => console.log(`Retry ${attempt}: ${error.message}`),
93+
},
94+
responseType: 'json',
95+
});
96+
```
97+
8398
## Examples
8499

85100
- [Basic](examples/basic.ts) - Basic fetch requests
86101
- [Progress Tracking](examples/with-progress.ts) - Download progress and chunk streaming
87102
- [Abortable](examples/abortable.ts) - Cancel and timeout requests
103+
- [Retry](examples/with-retry.ts) - Automatic retry strategies
88104
- [Error Handling](examples/error-handling.ts) - Error handling patterns
89105

90106
## License

examples/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ console.log();
1717
await import('./abortable.ts');
1818
console.log();
1919

20+
await import('./with-retry.ts');
21+
console.log();
22+
2023
await import('./error-handling.ts');
2124

2225
console.log('\n╔════════════════════════════════════════╗');

0 commit comments

Comments
 (0)