# Cancel requests

Use cancellation when a request is no longer needed, or when an extended client needs a different cancellation lifetime. [Ky](https://bench-ky-61.atloria.app/p/bench-ky-61-6TqdzXtmKC/developer/ky#default) builds on Fetch: pass an `AbortController`'s signal through the `signal` option, then call `abort()` to cancel.

## 1. Pass a signal and handle cancellation

This sample runs in a browser or Node.js with Fetch support. It follows the authors' cancellation example: start a request, cancel it with a timer, and distinguish cancellation from other failures. Use a slow endpoint so the request is still pending when the timer fires.

```ts
import ky from 'ky';

async function main() {
	const controller = new AbortController();
	const timer = setTimeout(() => {
		controller.abort();
	}, 500);

	try {
		const text = await ky.get('https://httpbin.org/delay/2', {
			signal: controller.signal,
		}).text();
		console.log(text);
	} catch (error) {
		if (error instanceof Error && error.name === 'AbortError') {
			console.log('Fetch aborted');
		} else {
			console.error('Fetch error:', error);
		}
	} finally {
		clearTimeout(timer);
	}
}

void main();
```

With a pending request, `abort()` makes the awaited operation reject with an error named `AbortError`, and the cancellation branch prints `Fetch aborted`. If the request finishes first, `.text()` returns the response body as a string instead. Keep other failures in a separate branch rather than treating every rejection as cancellation.

## 2. Replace an inherited signal

By default, `extend()` combines inherited and new signals: either controller can cancel the extended client's requests. Use [`replaceOption`](https://bench-ky-61.atloria.app/p/bench-ky-61-6TqdzXtmKC/developer/ky#replaceoption) when only the new controller must control the child client.

```ts
import ky, {replaceOption} from 'ky';

async function main() {
	const parentController = new AbortController();
	const replacementController = new AbortController();
	const api = ky.create({signal: parentController.signal});
	const childApi = api.extend({
		signal: replaceOption(replacementController.signal),
	});

	parentController.abort();

	try {
		const text = await childApi.get('https://httpbin.org/get').text();
		console.log(text);
	} catch (error) {
		console.error('Fetch error:', error);
	}

	const pending = childApi.get('https://httpbin.org/delay/2').text();
	replacementController.abort();

	try {
		await pending;
	} catch (error) {
		if (error instanceof Error && error.name === 'AbortError') {
			console.log('Child request aborted');
		} else {
			console.error('Fetch error:', error);
		}
	}
}

void main();
```

Aborting the parent does not cancel the child's first request. Look for your endpoint's response text. The replacement controller cancels the subsequent child request. `extend()` returns a new client; the parent retains its original defaults.

## 3. Remove an inherited signal

Pass `signal: undefined` to `extend()` to remove inherited signals without installing a replacement. The child can make requests even after the parent controller is aborted.

```ts
import ky from 'ky';

async function main() {
	const parentController = new AbortController();
	const api = ky.create({signal: parentController.signal});
	const independentApi = api.extend({signal: undefined});

	parentController.abort();

	try {
		const text = await independentApi.get('https://httpbin.org/get').text();
		console.log(text);
	} catch (error) {
		console.error('Fetch error:', error);
	}
}

void main();
```

The parent signal no longer cancels this request. You can still pass a fresh signal in an individual request's options to make that request cancellable.

## Options and pitfalls

The cancellation option belongs to [`Options`](https://bench-ky-61.atloria.app/p/bench-ky-61-6TqdzXtmKC/developer/ky-options#options).

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `signal` | `AbortSignal \| null \| undefined` | Not set | Supplies a Fetch cancellation signal. In `extend()`, a new signal combines with inherited signals; `replaceOption(signal)` replaces them, and explicit `undefined` removes them. |

- Create a fresh controller for a new cancellation lifetime. A request using an already-aborted signal rejects rather than starting a fresh operation.
- Signal combination requires `AbortSignal.any()`. Without it, Ky uses the last supplied signal instead of combining signals.
- The `AbortError` check above applies to `abort()` without a custom reason. If you pass a reason to `abort(reason)`, handle that reason: Ky surfaces the user signal's abort reason.

## Related

- [Instances and defaults](https://bench-ky-61.atloria.app/p/bench-ky-61-6TqdzXtmKC/developer/instances-and-defaults) explains how to compose client defaults.
- [Handle request errors](https://bench-ky-61.atloria.app/p/bench-ky-61-6TqdzXtmKC/developer/handle-request-errors) covers failures other than cancellation.
- [Retry failed requests](https://bench-ky-61.atloria.app/p/bench-ky-61-6TqdzXtmKC/developer/retry-failed-requests) covers retry policy and timeouts.
