Design Cancellable Function - Problem
In modern JavaScript applications, you often need to handle long-running asynchronous operations that users might want to cancel before completion. Think downloading large files, processing data, or making multiple API calls in sequence.
Your task is to implement a cancellable function that provides cooperative cancellation for generator-based async workflows. This function should:
- Accept a generator object that yields promises
- Return an array with a cancel function and a promise
- Handle promise resolution/rejection and pass values back to the generator
- Support cancellation by throwing
"Cancelled"string (not Error object) to the generator - Resolve with the final return value or handle cancellation gracefully
Example usage:
function* tasks() {
const val = yield new Promise(resolve => resolve(2 + 2));
yield new Promise(resolve => setTimeout(resolve, 100));
return val + 1;
}
const [cancel, promise] = cancellable(tasks());
setTimeout(cancel, 50); // Cancel after 50ms
promise.catch(console.log); // logs "Cancelled" at t=50msIf cancel() wasn't called, the promise would resolve to 5 after 100ms.
Input & Output
basic_cancellation.js โ Basic Cancellation
$
Input:
function* tasks() {
const val = yield new Promise(resolve => resolve(2 + 2));
yield new Promise(resolve => setTimeout(resolve, 100));
return val + 1;
}
const [cancel, promise] = cancellable(tasks());
setTimeout(cancel, 50);
โบ
Output:
Promise rejects with "Cancelled"
๐ก Note:
The generator is cancelled after 50ms, before the 100ms timeout completes. The promise rejects with the string "Cancelled".
successful_completion.js โ No Cancellation
$
Input:
function* tasks() {
const val = yield new Promise(resolve => resolve(3));
yield new Promise(resolve => setTimeout(resolve, 50));
return val * 2;
}
const [cancel, promise] = cancellable(tasks());
// No cancel call
โบ
Output:
Promise resolves with 6
๐ก Note:
Without cancellation, the generator completes normally. First promise resolves with 3, second completes after 50ms, then returns 3 * 2 = 6.
error_handling.js โ Generator Error
$
Input:
function* tasks() {
yield new Promise((resolve, reject) => reject(new Error("Network error")));
return "Should not reach here";
}
const [cancel, promise] = cancellable(tasks());
โบ
Output:
Promise rejects with Error("Network error")
๐ก Note:
When a yielded promise rejects, the error is thrown back to the generator. If not caught, the main promise rejects with that error.
Visualization
Tap to expand
Understanding the Visualization
1
Initialize
Set up the generator execution context and cancellation signal system
2
Execute Step
Start processing current generator step, yielding a promise
3
Race Condition
Race the promise resolution against cancellation signal using Promise.race()
4
Handle Outcome
If cancelled, throw 'Cancelled' to generator; if resolved, pass value to generator.next()
5
Continue/Complete
Either continue to next step or complete execution based on generator state
Key Takeaway
๐ฏ Key Insight: The optimal solution races promise resolution against cancellation signals, enabling immediate response while maintaining proper generator lifecycle management through cooperative cancellation patterns.
Time & Space Complexity
Time Complexity
O(n)
Where n is the number of generator steps, each processed once with optimal cancellation
โ Linear Growth
Space Complexity
O(1)
Constant space for cancellation state and current execution context
โ Linear Space
Constraints
- Generator function only yields promises (as stated in problem)
- Cancellation error must be the string "Cancelled" (not Error object)
- Must handle promise resolution and rejection properly
- Must support cancellation at any point during execution
- Must properly clean up resources and handle generator lifecycle
๐ก
Explanation
AI Ready
๐ก Suggestion
Tab
to accept
Esc
to dismiss
// Output will appear here after running code