Skip to main content

Cancelling Tasks

Stopping running tasks and freeing resources

API

Overview

One of the key features of Tasks is their ability to be cancelled. Cancellation means that:

  • The current execution does not need to resolve or reject
  • The execution can be stopped
  • Resources can be freed (timers, network requests, file handles, etc.)

Making a Task Cancellable

Any task can be configured as cancellable by adding an abort listener to canceler value with a callback function.

Basic Pattern

import { Task } from '@w5s/task';

const cancellableTask = Task.create(({ canceler }) => {
// Setup your async operation
const timeoutId = setTimeout(() => {/* ... */}, 1000);

// Define what happens when cancelled
canceler.addEventListener('abort', () => {
clearTimeout(timeoutId);
console.log('Task was cancelled');
};

return Task.ok('result');
});

With Fetch API (AbortController)

import { Task } from '@w5s/task';

const fetchAPIData = (): Task<string, NetworkError> => Task.create(async ({ canceler }) => {
const abortController = new AbortController();
// Set the canceler to abort the request when the task is cancelled
canceler.addEventListener('abort', () => abortController.abort());

try {
const response = await fetch('/api/data', { signal: abortController.signal });
return Task.ok(await response.text());
} catch (error) {
return Task.error(new NetworkError({ cause: error }));
}
});

With Task.from (Low-level)

import { Task } from '@w5s/task';

const delayedTask = Task.from(({ resolve, reject, canceler }) => {
const timeoutId = setTimeout(() => resolve(42), 1000);

// Setup cancellation
canceler.addEventListener('abort', () => clearTimeout(timeoutId));
});

Triggering Cancellation

Using a Canceler Ref

Pass a canceler reference to Task.run to be able to cancel the task later:

import { Task } from '@w5s/task';

// Create a canceler reference
const controller = new AbortController();

// Start the task with the canceler
const resultPromise = Task.run(longRunningTask, { signal: controller.signal });

// Cancel after 5 seconds
setTimeout(() => {
controller.abort();
}, 5000);

Timeout Pattern

Use @w5s/task-timeout to automatically cancel a task after a delay:

import { Task } from '@w5s/task';
import { timeout } from '@w5s/task-timeout';
import { TimeDuration } from '@w5s/time';

const longTask = fetchLargeData();

// Cancel if not completed within 30 seconds
const timeoutTask = timeout(longTask, TimeDuration({ seconds: 30 }));

const result = await Task.run(timeoutTask);
// Result.Error(TimeoutError) if task takes longer than 30 seconds

Best Practices

tip

Always Clean Up Resources

When creating cancellable tasks, ensure all resources are properly released:

const task = Task.create(async ({ canceler }) => {
const connection = await openConnection();
const subscription = events.subscribe(handler);

// Clean up everything on cancel
canceler.addEventListener('abort', () => {
connection.close();
subscription.unsubscribe();
};

// ... task logic
});
tip

Check for Cancellation in Long Operations

For tasks with multiple steps, check if cancellation was requested:

const processItems = (items: Item[]) => Task.create(async ({ canceler }) => {
let isCancelled = false;
canceler.addEventListener('abort', () => { isCancelled = true; });

const results = [];
for (const item of items) {
if (isCancelled) {
// Stop processing if cancelled
break;
}
results.push(await processItem(item));
}

return Task.ok(results);
});
tip

Propagate Cancellation to Subtasks

When running subtasks, ensure cancellation propagates:

const parentTask = Task.create(async ({ canceler, run }) => {
// The `run` function automatically propagates the canceler
const result1 = await run(childTask1);
const result2 = await run(childTask2);

return Task.ok({ result1, result2 });
});

FAQ

What happens to the result when a task is cancelled?

When a task is cancelled, it neither resolves nor rejects. The Promise returned by Task.run will remain pending unless the task handles cancellation by resolving or rejecting explicitly.

Can I know if a task was cancelled?

You can track cancellation state within your task:

const task = Task.create(async ({ canceler }) => {
let wasCancelled = false;

canceler.addEventListener('abort', () => {
wasCancelled = true;
console.log('Task was cancelled');
};

// ... task logic

if (wasCancelled) {
return Task.error(new CancelledError());
}

return Task.ok(result);
});

Does cancellation work with async/await?

Yes, but you need to handle it properly. When using AbortController with fetch or similar APIs, the abort will cause the Promise to reject with an AbortError:

const task = Task.create(async ({ canceler }) => {
try {
const response = await fetch('/api', { signal: canceler });
return Task.ok(await response.json());
} catch (error) {
if (error.name === 'AbortError') {
// Handle cancellation specifically
return Task.error(new CancelledError());
}
return Task.error(new NetworkError({ cause: error }));
}
});