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=50ms

If 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
InitializeExecute StepPromise.race()CancelledResolvedHandle OutcomeContinue?Complete Execution๐Ÿš€ StartโŒ Cancel Signalโœ… Promise DoneLoop Back
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

n
2n
โœ“ Linear Growth
Space Complexity
O(1)

Constant space for cancellation state and current execution context

n
2n
โœ“ 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
Asked in
Google 45 Meta 38 Netflix 32 Microsoft 28
28.5K Views
Medium Frequency
~35 min Avg. Time
892 Likes
Ln 1, Col 1
Smart Actions
๐Ÿ’ก Explanation
AI Ready
๐Ÿ’ก Suggestion Tab to accept Esc to dismiss
// Output will appear here after running code
Code Editor Closed
Click the red button to reopen