ffetch can work without native AbortSignal.timeout because it has an internal timeout fallback. Signal combination requires AbortSignal.any (native or polyfill).
- Node.js v18.8.0+: Full native support for
AbortSignal.timeout - Node.js v20.6.0+: Full native support for both
AbortSignal.timeoutandAbortSignal.any
For older Node.js versions, you must install a polyfill for AbortSignal.any.
AbortSignal.timeout polyfill is optional because ffetch has an internal timeout fallback:
npm install abortcontroller-polyfill abort-controller-xThen ensure the APIs are available globally before importing ffetch:
// Optional: abortcontroller-polyfill (for native-style AbortSignal.timeout)
require('abortcontroller-polyfill/dist/polyfill-patch-fetch')
// Required when AbortSignal.any is missing
import 'abort-controller-x/polyfill'
// Now you can use ffetch
import { createClient } from '@fetchkit/ffetch'// Node.js automatically handles HTTP/HTTPS protocols
const client = createClient()
await client('https://api.example.com') // Works
await client('http://localhost:3000') // Worksimport https from 'https'
import fetch from 'node-fetch'
const agent = new https.Agent({
keepAlive: true,
timeout: 5000,
})
// Wrap your fetch implementation and inject transport-specific options there.
const fetchWithAgent = (input, init) => fetch(input, { ...init, agent })
const client = createClient({ fetchHandler: fetchWithAgent })
await client('https://api.example.com')agent is fetch-implementation-specific and not part of standard RequestInit. Prefer configuring it inside fetchHandler.
// For development only - never use in production
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
// Better approach: configure a custom agent in fetchHandler
import https from 'https'
import fetch from 'node-fetch'
const agent = new https.Agent({
rejectUnauthorized: false,
})
const client = createClient({
fetchHandler: (input, init) => fetch(input, { ...init, agent }),
})
await client('https://localhost:8443')- Chrome 117+: Native support for required AbortSignal APIs
- Firefox 117+: Native support for required AbortSignal APIs
- Safari 17+: Native support for required AbortSignal APIs
- Edge 117+: Native support for required AbortSignal APIs
Older browsers can still work when AbortSignal.any is polyfilled. AbortSignal.timeout polyfill is optional:
<!-- Include polyfills before your app -->
<script src="https://unpkg.com/abortcontroller-polyfill/dist/polyfill.min.js"></script>
<script src="https://unpkg.com/abort-controller-x/dist/polyfill.umd.js"></script>
<!-- Your app -->
<script type="module">
import { createClient } from 'https://unpkg.com/@fetchkit/ffetch/dist/index.min.js'
// ... your code
</script>// Works in service workers
self.addEventListener('fetch', async (event) => {
if (event.request.url.includes('/api/')) {
const client = createClient({ timeout: 5000 })
const response = await client(event.request)
event.respondWith(response)
}
})// Use a module worker and import the ESM build
import { createClient } from 'https://unpkg.com/@fetchkit/ffetch/dist/index.min.js'
const client = createClient()
self.postMessage(await client('/api/data').then((r) => r.json()))const client = createClient()
// CORS requests work transparently
await client('https://api.external.com/data', {
mode: 'cors',
credentials: 'include',
})ffetch automatically adapts to the environment and can wrap any fetch-compatible implementation:
// Automatically detects environment and uses appropriate fetch implementation
// Or pass your own fetch-compatible implementation for SSR, edge, or custom environments
const client = createClient() // Uses global fetch by default
// Example: Use node-fetch, undici, or framework-provided fetch
import fetch from 'node-fetch'
const clientNode = createClient({ fetchHandler: fetch })
// Works in Node.js, browsers, workers, SSR, edge, etc.
const response = await client('https://api.example.com')// jest.config.js
module.exports = {
setupFilesAfterEnv: ['<rootDir>/test-setup.js'],
}
// test-setup.js
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'happy-dom', // or 'jsdom'
setupFiles: ['./test-setup.ts'],
},
})
// test-setup.ts
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch'// Works out of the box in modern browsers
// For older browser testing, include polyfills in your test pagesfunction checkCompatibility() {
if (typeof AbortSignal === 'undefined') {
throw new Error('AbortSignal not supported. Please add a polyfill.')
}
if (typeof AbortSignal.any !== 'function') {
throw new Error(
'AbortSignal.any is required for combining multiple signals. Please install a polyfill.'
)
}
}
// Check before creating client
checkCompatibility()
const client = createClient()// Fallback for environments without full AbortSignal support
const client = createClient({
timeout: typeof AbortSignal?.timeout === 'function' ? 5000 : undefined,
// ... other options
})<script type="module">
import { createClient } from 'https://unpkg.com/@fetchkit/ffetch/dist/index.min.js'
const client = createClient()
const data = await client('/api/data').then((r) => r.json())
</script>ffetch does not currently publish a UMD build. Use the ESM build shown above (or import from the package in a bundler/runtime environment).
import { useEffect, useState } from 'react'
import { createClient } from '@fetchkit/ffetch'
const client = createClient({ timeout: 5000 })
function DataComponent() {
const [data, setData] = useState(null)
useEffect(() => {
const abortController = new AbortController()
client('/api/data', { signal: abortController.signal })
.then((r) => r.json())
.then(setData)
.catch((err) => {
if (err.name !== 'AbortError') {
console.error(err)
}
})
return () => abortController.abort()
}, [])
return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>
}<template>
<div>{{ data || 'Loading...' }}</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { createClient } from '@fetchkit/ffetch'
const client = createClient({ timeout: 5000 })
const data = ref(null)
let abortController
onMounted(async () => {
abortController = new AbortController()
try {
const response = await client('/api/data', {
signal: abortController.signal,
})
data.value = await response.json()
} catch (err) {
if (err.name !== 'AbortError') {
console.error(err)
}
}
})
onUnmounted(() => {
abortController?.abort()
})
</script><script>
import { onMount, onDestroy } from 'svelte'
import { createClient } from '@fetchkit/ffetch'
const client = createClient({ timeout: 5000 })
let data = null
let abortController
onMount(async () => {
abortController = new AbortController()
try {
const response = await client('/api/data', {
signal: abortController.signal
})
data = await response.json()
} catch (err) {
if (err.name !== 'AbortError') {
console.error(err)
}
}
})
onDestroy(() => {
abortController?.abort()
})
</script>
<div>{data ? JSON.stringify(data) : 'Loading...'}</div>For SvelteKit, Next.js, and Nuxt, it is recommended to pass the fetch instance provided by the framework in your handler or context for SSR-safe behavior. The parameter name may vary (often fetch, but check your framework docs).
SvelteKit example:
// In load functions, actions, or endpoints, use the provided fetch
export async function load({ fetch }) {
const client = createClient({ fetchHandler: fetch })
// Use client for SSR-safe requests
}
// In endpoints
export async function GET({ fetch }) {
const client = createClient({ fetchHandler: fetch })
// ...
}Nuxt example:
// In server routes, use event.fetch
export default defineEventHandler((event) => {
const client = createClient({ fetchHandler: event.fetch })
// ...
})Next.js edge API route (if fetch is provided):
export default async function handler(request) {
const client = createClient({ fetchHandler: request.fetch })
// ...
}Recommended: use the fetch instance provided by the framework in handler/context code. ffetch also supports any fetch-compatible implementation and falls back to global fetch when no
fetchHandleris provided.
ffetch has an internal fallback, so this is usually non-fatal.
Optional: add a polyfill for AbortSignal.timeout
npm install abortcontroller-polyfill
Solution: Either upgrade to Node.js 20.6+ or add a polyfill
npm install abort-controller-x
This is expected in some environments. Check for both error types:
if (err instanceof TimeoutError || err instanceof AbortError) {
// Handle timeout
}
Solution: Ensure your server sends proper CORS headers
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
You can add verbose logging to troubleshoot issues:
const client = createClient({
hooks: {
before: (req) => console.log('→', req.method, req.url),
after: (req, res) => console.log('←', res.status),
onError: (req, err) => console.error('✗', err),
},
})