onCancel()
Register a callback to execute when the task is cancelled.
Syntax
typescript
task.onCancel(callback: () => void): FuturableTask<T>Parameters
callback
A function to execute when the task is cancelled. The callback receives no arguments.
Return Value
Returns this for method chaining.
Description
The onCancel() method registers a callback that will be executed when the task is cancelled via cancel(). Multiple callbacks can be registered and they will be executed in the order they were added.
Key Behaviors
- Eager Registration: Callbacks are registered at the task level, not per execution
- Multiple Callbacks: Can register multiple callbacks on the same task
- Execution Order: Callbacks execute in registration order
- Chainable: Returns
thisfor fluent API - One-Time: Each callback executes once when cancelled
Examples
Basic Usage
typescript
const task = FuturableTask
.of(() => longOperation())
.onCancel(() => {
console.log('Task was cancelled');
});
task.cancel(); // Logs: "Task was cancelled"Resource Cleanup
typescript
const task = FuturableTask.of((resolve, reject) => {
const ws = new WebSocket('wss://example.com');
const timer = setTimeout(() => resolve('done'), 5000);
// Cleanup on cancellation
task.onCancel(() => {
clearTimeout(timer);
ws.close();
console.log('Resources cleaned up');
});
ws.onmessage = (msg) => resolve(msg.data);
});Multiple Callbacks
typescript
const task = FuturableTask
.of(() => fetchData())
.onCancel(() => console.log('Cleanup 1'))
.onCancel(() => console.log('Cleanup 2'))
.onCancel(() => console.log('Cleanup 3'));
task.cancel();
// Logs in order:
// "Cleanup 1"
// "Cleanup 2"
// "Cleanup 3"Method Chaining
typescript
const task = FuturableTask
.of(() => fetchData())
.map(data => processData(data))
.onCancel(() => console.log('Cancelled'))
.retry(3)
.timeout(5000)
.onCancel(() => console.log('All cleanup done'));State Management
typescript
let isRunning = false;
const task = FuturableTask
.of(() => {
isRunning = true;
return heavyComputation();
})
.onCancel(() => {
isRunning = false;
console.log('Computation stopped');
});
const execution = task.run();
// Cancel and update state
setTimeout(() => task.cancel(), 1000);UI Integration
typescript
function LoadingButton({ onClick }) {
const [loading, setLoading] = useState(false);
const handleClick = () => {
const task = FuturableTask
.of(() => performAction())
.onCancel(() => {
setLoading(false);
showMessage('Action cancelled');
});
setLoading(true);
task.run()
.then(() => setLoading(false))
.catch(() => setLoading(false));
// Save task reference for cancellation
window.currentTask = task;
};
return (
<button onClick={handleClick} disabled={loading}>
{loading ? 'Loading...' : 'Click Me'}
</button>
);
}File Upload Cancellation
typescript
const uploadTask = FuturableTask
.of(() => {
const formData = new FormData();
formData.append('file', file);
return fetch('/upload', { method: 'POST', body: formData });
})
.onCancel(() => {
console.log('Upload cancelled');
updateProgressBar(0);
showNotification('Upload cancelled by user');
});
// User clicks cancel button
cancelButton.addEventListener('click', () => {
uploadTask.cancel();
});Database Transaction Rollback
typescript
const transaction = FuturableTask
.of(async () => {
await db.begin();
await db.execute('INSERT INTO users ...');
await db.execute('UPDATE accounts ...');
await db.commit();
})
.onCancel(async () => {
console.log('Rolling back transaction');
await db.rollback();
});Network Request Abort
typescript
const controller = new AbortController();
const task = FuturableTask
.of(() => fetch('/api/data', { signal: controller.signal }))
.onCancel(() => {
controller.abort();
console.log('Network request aborted');
});Timer Cleanup
typescript
const task = FuturableTask.of((resolve) => {
const timers: NodeJS.Timeout[] = [];
timers.push(setTimeout(() => console.log('Step 1'), 1000));
timers.push(setTimeout(() => console.log('Step 2'), 2000));
timers.push(setTimeout(() => resolve('done'), 3000));
// Cleanup all timers on cancel
task.onCancel(() => {
timers.forEach(timer => clearTimeout(timer));
console.log('All timers cleared');
});
});EventSource Cleanup
typescript
const task = FuturableTask.of(() => {
const eventSource = new EventSource('/events');
return new Promise((resolve) => {
eventSource.onmessage = (event) => {
resolve(event.data);
};
});
}).onCancel(() => {
eventSource.close();
console.log('EventSource closed');
});Combining with Executor onCancel
You can use both executor-level and task-level cancellation:
typescript
const task = new FuturableTask((resolve, reject, { onCancel }) => {
const timer = setTimeout(() => resolve('done'), 5000);
// Executor-level cleanup (runs per execution)
onCancel(() => {
clearTimeout(timer);
console.log('Execution cancelled');
});
});
// Task-level cleanup (runs once for the task)
task.onCancel(() => {
console.log('Task cancelled');
});
task.cancel();
// Logs:
// "Task cancelled" (task-level)
// "Execution cancelled" (executor-level, for any running execution)Common Patterns
Cleanup Manager
typescript
class ResourceManager {
private resources: any[] = [];
createTask<T>(work: () => T) {
return FuturableTask.of(work).onCancel(() => {
this.cleanup();
});
}
addResource(resource: any) {
this.resources.push(resource);
}
cleanup() {
this.resources.forEach(r => r.dispose());
this.resources = [];
}
}Progress Reset
typescript
const task = FuturableTask
.of(() => processLargeFile())
.onCancel(() => {
progressBar.reset();
statusText.innerText = 'Processing cancelled';
});Analytics Tracking
typescript
const task = FuturableTask
.of(() => performOperation())
.onCancel(() => {
analytics.track('operation_cancelled', {
timestamp: Date.now(),
reason: 'user_initiated'
});
});Notification Display
typescript
const task = FuturableTask
.of(() => longProcess())
.onCancel(() => {
toast.show({
message: 'Operation cancelled',
type: 'warning',
duration: 3000
});
});Best Practices
1. Always Cleanup Resources
typescript
// ✅ Good - cleanup all resources
const task = FuturableTask
.of(() => {
const resource = allocate();
return process(resource);
})
.onCancel(() => {
resource.dispose();
});
// ❌ Bad - resource leak
const task = FuturableTask.of(() => {
const resource = allocate();
return process(resource);
});2. Keep Callbacks Simple
typescript
// ✅ Good - simple, focused callback
.onCancel(() => {
cleanup();
updateUI();
})
// ❌ Bad - complex logic
.onCancel(() => {
if (condition1) {
// lots of logic
} else if (condition2) {
// more logic
}
// ...
})3. Don't Throw in Callbacks
typescript
// ✅ Good - handle errors internally
.onCancel(() => {
try {
riskyCleanup();
} catch (error) {
console.error('Cleanup failed:', error);
}
})
// ❌ Bad - throwing errors
.onCancel(() => {
riskyCleanup(); // May throw
})4. Use for Side Effects Only
typescript
// ✅ Good - side effects only
.onCancel(() => {
logCancellation();
closeConnections();
})
// ❌ Bad - trying to change task behavior
.onCancel(() => {
return 'cancelled'; // Ignored
})Execution Timing
typescript
const task = FuturableTask
.of(() => work())
.onCancel(() => console.log('1'))
.onCancel(() => console.log('2'));
console.log('Before cancel');
task.cancel();
console.log('After cancel');
// Output:
// "Before cancel"
// "1"
// "2"
// "After cancel"Notes
- Callbacks are executed synchronously when
cancel()is called - Callbacks execute even if the task was never run
- Return values from callbacks are ignored
- Errors thrown in callbacks are not caught (wrap in try-catch)
- Multiple calls to
onCancel()add more callbacks - Callbacks are called only once per
cancel()invocation - Works in combination with executor-level
onCancel()
See Also
- cancel() - Cancel the task
- signal - Access the abort signal
- Constructor - Executor-level onCancel
- run() - Execute the task
