LeetCode Problem Workspace

Design Cancellable Function

Master designing cancellable functions with generators, handling promise flow, cancellation, and error propagation effectively in JavaScript.

category

0

Topics

code_blocks

1

Code langs

hub

0

Related

Practice Focus

Hard · Design Cancellable Function core interview pattern

bolt

Answer-first summary

Master designing cancellable functions with generators, handling promise flow, cancellation, and error propagation effectively in JavaScript.

Interview AiBox logo

Ace coding interviews with Interview AiBox guidance for Design Cancellable Function core interview pattern

Try AiBox Copilotarrow_forward

This problem focuses on creating a cancellable function that wraps a generator yielding promises. You must ensure two-way communication between the generator and the promises, passing resolved values back or throwing errors when promises reject. Properly handling the cancel callback is essential, as it should throw a "Cancelled" string into the generator, ensuring no further code executes and the returned promise resolves or rejects correctly.

Problem Statement

You are asked to implement a function called cancellable that accepts a generator object which yields promises. Your function should return an array containing two elements: a cancel function and a promise representing the generator's final result. The promise should resolve or reject according to the generator's flow, and each yielded promise's resolved value should be passed back into the generator. If a yielded promise rejects, that error should be thrown back into the generator.

If the cancel function is invoked before the generator completes, the generator should immediately receive a thrown string "Cancelled". If the generator catches this error, the returned promise continues with the next yielded value or returned value. If the generator does not catch it, the returned promise must reject with "Cancelled". No further code in the generator should run after cancellation. Assume all generators yield only promises and are properly structured.

Examples

Example 1

Input: See original problem statement.

Output: See original problem statement.

function* tasks() { const val = yield new Promise(resolve => resolve(2 + 2)); yield new Promise(resolve => setTimeout(resolve, 100)); return val + 1; // calculation shouldn't be done. } const [cancel, promise] = cancellable(tasks()); setTimeout(cancel, 50); promise.catch(console.log); // logs "Cancelled" at t=50ms

Example 2

Input: generatorFunction = function*() { return 42; } cancelledAt = 100

Output: {"resolved": 42}

const generator = generatorFunction(); const [cancel, promise] = cancellable(generator); setTimeout(cancel, 100); promise.then(console.log); // resolves 42 at t=0ms

The generator immediately yields 42 and finishes. Because of that, the returned promise immediately resolves 42. Note that cancelling a finished generator does nothing.

Example 3

Input: generatorFunction = function*() { const msg = yield new Promise(res => res("Hello")); throw Error: ${msg}; } cancelledAt = null

Output: {"rejected": "Error: Hello"}

A promise is yielded. The function handles this by waiting for it to resolve and then passes the resolved value back to the generator. Then an error is thrown which has the effect of causing the promise to reject with the same thrown error.

Constraints

  • cancelledAt == null or 0 <= cancelledAt <= 1000
  • generatorFunction returns a generator object

Solution Approach

Wrap Generator with Async Loop

Create an async function that iteratively calls next or throw on the generator. Await each yielded promise and send its resolved value back. Catch promise rejections and throw them into the generator to preserve error propagation.

Implement Cancel Function

Maintain a flag indicating cancellation. The cancel function should throw the string "Cancelled" into the generator if called before completion. Ensure the generator's catch blocks handle it correctly to resolve the returned promise, otherwise reject immediately.

Connect Promises and Generator Flow

Ensure the returned promise resolves when the generator finishes normally or rejects when an uncaught error occurs. Properly propagate both cancellation and generator-thrown errors to the promise, while avoiding executing further code in the generator after cancellation.

Complexity Analysis

Metric Value
Time Depends on the final approach
Space Depends on the final approach

Time and space complexity depend on the number of yielded promises and the overhead of awaiting each. Each promise is handled sequentially, and memory is used to store state and intermediate values.

What Interviewers Usually Probe

  • Expect candidates to recognize the two-way communication between generator and async evaluation.
  • Watch for proper handling of promise rejections thrown back into the generator.
  • Ensure cancellation stops generator execution and resolves or rejects the promise appropriately.

Common Pitfalls or Variants

Common pitfalls

  • Failing to propagate promise rejections back into the generator correctly.
  • Allowing generator code to continue running after cancellation is invoked.
  • Returning a resolved promise instead of rejecting when cancellation is uncaught.

Follow-up variants

  • Support multiple simultaneous cancellable generators with independent cancel functions.
  • Modify to allow cancellation with custom error messages instead of "Cancelled".
  • Extend to handle synchronous yields alongside promises in the generator.

FAQ

What does the cancel function do in the Design Cancellable Function problem?

It throws the string "Cancelled" into the generator. If uncaught, the returned promise rejects immediately and no further code executes.

Can the generator yield non-promise values?

No, the generator is assumed to yield only promises. Handling non-promises is not required for this problem.

What happens if a yielded promise rejects?

The rejection is thrown into the generator, allowing it to handle the error. If uncaught, the returned promise rejects with that error.

Does cancelling a completed generator affect the promise?

No, if the generator has already finished, calling cancel does nothing and the returned promise remains resolved or rejected as usual.

Why is two-way communication important in this problem?

It ensures that each yielded promise's result is correctly passed back and that errors, including cancellation, are propagated properly to maintain correct generator behavior.

terminal

Solution

Solution 1

#### TypeScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function cancellable<T>(generator: Generator<Promise<any>, T, unknown>): [() => void, Promise<T>] {
    let cancel: () => void = () => {};
    const cancelPromise = new Promise((resolve, reject) => {
        cancel = () => reject('Cancelled');
    });
    cancelPromise.catch(() => {});

    const promise = (async () => {
        let next = generator.next();
        while (!next.done) {
            try {
                next = generator.next(await Promise.race([next.value, cancelPromise]));
            } catch (e) {
                next = generator.throw(e);
            }
        }
        return next.value;
    })();

    return [cancel, promise];
}

/**
 * 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);
 * promise.catch(console.log); // logs "Cancelled" at t=50ms
 */
Design Cancellable Function Solution: Design Cancellable Function core inte… | LeetCode #2650 Hard