Skip to content

Commit 19bdae4

Browse files
committed
AbortSignal.any polyfill removed
1 parent abcc2f3 commit 19bdae4

File tree

8 files changed

+32
-146
lines changed

8 files changed

+32
-146
lines changed

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,21 @@ try {
122122

123123
`ffetch` requires modern AbortSignal APIs:
124124

125-
- **Node.js 18.8+** (or polyfill for older versions)
126-
- **Modern browsers** (Chrome 88+, Firefox 89+, Safari 15.4+, Edge 88+)
125+
- **Node.js 20.6+** (for AbortSignal.any)
126+
- **Modern browsers** (Chrome 117+, Firefox 117+, Safari 17+, Edge 117+)
127127

128-
For older environments, see the [compatibility guide](./docs/compatibility.md).
128+
If your environment does not support `AbortSignal.any` (Node.js < 20.6, older browsers), you **must install a polyfill** before using ffetch. See the [compatibility guide](./docs/compatibility.md) for instructions.
129129

130130
**Custom fetch support:**
131131
You can pass any fetch-compatible implementation (native fetch, node-fetch, undici, SvelteKit, Next.js, Nuxt, or a polyfill) via the `fetchHandler` option. This makes ffetch fully compatible with SSR, edge, metaframework environments, custom backends, and test runners.
132132

133+
#### "AbortSignal.any is not a function"
134+
135+
```
136+
Solution: Install a polyfill for AbortSignal.any
137+
npm install abort-controller-x
138+
```
139+
133140
## CDN Usage
134141

135142
```html

docs/advanced.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ Pending requests and abort logic work identically whether you use the default gl
4444

4545
Every `PendingRequest` always has a `controller` property, even if you did not supply an AbortController. This allows you to abort any pending request programmatically, regardless of how it was created.
4646

47-
When multiple signals (user, timeout, transformRequest) are combined and `AbortSignal.any` is not available, ffetch creates a new internal `AbortController` to manage aborts. This controller is always available in `PendingRequest.controller`.
48-
49-
You can always abort a pending request using `pendingRequest.controller.abort()`, even if you did not provide a controller or signal. This works for all requests tracked in `pendingRequests`.
47+
Signal combination (user, timeout, transformRequest) requires `AbortSignal.any`. If your environment does not support it, you must install a polyfill before using ffetch.
5048

5149
You can access and monitor all active requests through the `pendingRequests` property on the client instance:
5250

docs/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ client.abortAll(): void // Aborts all currently pending requests
8282

8383
### Notes
8484

85+
- Signal combination (user, timeout, transformRequest) requires `AbortSignal.any`. If your environment does not support it, you must install a polyfill before using ffetch.
8586
- The first retry attempt uses `attempt = 2` (i.e., the first call is attempt 1, first retry is 2)
8687
- `shouldRetry` default logic: retries on network errors, HTTP 5xx, or 429; does not retry on 4xx (except 429), abort, or timeout errors
8788
- All client options can be overridden on a per-request basis via the `init` parameter

docs/compatibility.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Prerequisites
44

5-
`ffetch` requires modern AbortSignal APIs, specifically `AbortSignal.timeout` and `AbortSignal.any`.
5+
`ffetch` requires modern AbortSignal APIs, specifically `AbortSignal.timeout` and **AbortSignal.any**. Signal combination requires `AbortSignal.any` (native or polyfill).
66

77
## Node.js Support
88

@@ -13,21 +13,19 @@
1313

1414
### Polyfills for Older Versions
1515

16-
For older Node.js versions, you must install a polyfill:
16+
For older Node.js versions, you must install a polyfill for both `AbortSignal.timeout` and `AbortSignal.any`:
1717

1818
```bash
19-
npm install abortcontroller-polyfill
20-
# or
21-
npm install abort-controller-x
19+
npm install abortcontroller-polyfill abort-controller-x
2220
```
2321

2422
Then ensure the APIs are available globally before importing `ffetch`:
2523

2624
```javascript
27-
// Option 1: abortcontroller-polyfill
25+
// Option 1: abortcontroller-polyfill (for AbortSignal.timeout)
2826
require('abortcontroller-polyfill/dist/polyfill-patch-fetch')
2927

30-
// Option 2: abort-controller-x
28+
// Option 2: abort-controller-x (for AbortSignal.any)
3129
import 'abort-controller-x/polyfill'
3230

3331
// Now you can use ffetch
@@ -209,7 +207,9 @@ function checkCompatibility() {
209207
}
210208

211209
if (typeof AbortSignal.any !== 'function') {
212-
console.warn('AbortSignal.any not supported. Some features may not work.')
210+
throw new Error(
211+
'AbortSignal.any is required for combining multiple signals. Please install a polyfill.'
212+
)
213213
}
214214
}
215215

docs/migration.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,13 +299,15 @@ const client = createClient({
299299
const controller = new AbortController()
300300
const response = await fetch(url, { signal: controller.signal })
301301

302-
// ffetch - combines multiple signals automatically
302+
// ffetch - combines multiple signals automatically using AbortSignal.any
303+
// Requires AbortSignal.any (native or polyfill)
303304
const controller = new AbortController()
304305
const response = await client(url, {
305306
signal: controller.signal, // User signal
306307
timeout: 5000, // Creates timeout signal
307308
// Both signals are automatically combined
308309
})
310+
// If AbortSignal.any is not available, you must install a polyfill before using ffetch
309311
```
310312

311313
### 2. **Error Instance Checks**

src/client.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -96,35 +96,22 @@ export function createClient(opts: FFetchOptions = {}): FFetch {
9696
}
9797
if (timeoutSignal) signals.push(timeoutSignal)
9898

99+
// Use AbortSignal.any for signal combination. Requires native support or a polyfill.
100+
// If not available, instruct users to install a polyfill for environments lacking AbortSignal.any.
99101
if (signals.length === 0) {
100102
combinedSignal = undefined
103+
controller = new AbortController()
101104
} else if (signals.length === 1) {
102105
combinedSignal = signals[0]
103-
// Always create a new AbortController for tracking
104106
controller = new AbortController()
105107
} else {
106-
// Multiple signals need to be combined
107-
if (typeof AbortSignal.any === 'function') {
108-
combinedSignal = AbortSignal.any(signals)
109-
controller = new AbortController()
110-
} else {
111-
// Manual fallback: create a controller that aborts when any signal aborts
112-
controller = new AbortController()
113-
combinedSignal = controller.signal
114-
115-
// If any signal is already aborted, abort immediately
116-
if (signals.some((signal) => signal.aborted)) {
117-
controller.abort()
118-
} else {
119-
// Listen for abort events on all signals
120-
const abortHandler = () => {
121-
if (controller) controller.abort()
122-
}
123-
signals.forEach((signal) => {
124-
signal.addEventListener('abort', abortHandler, { once: true })
125-
})
126-
}
108+
if (typeof AbortSignal.any !== 'function') {
109+
throw new Error(
110+
'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'
111+
)
127112
}
113+
combinedSignal = AbortSignal.any(signals)
114+
controller = new AbortController()
128115
}
129116
const retryWithHooks = async () => {
130117
const effectiveRetries = init.retries ?? clientDefaultRetries

test/client.abort-fallback.test.ts

Lines changed: 0 additions & 91 deletions
This file was deleted.

test/client.error.test.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -240,22 +240,4 @@ describe('Advanced/Edge Cases: Custom Errors', () => {
240240
expect(err.message).toBe('Retry limit reached')
241241
}
242242
})
243-
244-
it('throws AbortError if combinedSignal.aborted is true and throwIfAborted is missing', async () => {
245-
// Remove AbortSignal.any to force fallback branch
246-
const origAny = AbortSignal.any
247-
// @ts-expect-error: Simulate missing AbortSignal.any for fallback branch coverage
248-
AbortSignal.any = undefined
249-
250-
// Use a minimal fake signal for branch coverage; cast to AbortSignal for test only
251-
const fakeSignal = { aborted: true } as unknown as AbortSignal
252-
253-
const f = createClient()
254-
await expect(
255-
f('https://example.com', { signal: fakeSignal })
256-
).rejects.toThrowError(new AbortError('Request was aborted by user'))
257-
258-
// Restore AbortSignal.any
259-
AbortSignal.any = origAny
260-
})
261243
})

0 commit comments

Comments
 (0)