Problem Description
Write a function cancellable that accepts a generator object which yields only promises. The function should return an array containing two items: a cancel function and a promise. As the generator yields promises, your function must wait for each promise to resolve (or reject) and feed the result (or error) back into the generator. If the cancel function is invoked before the generator completes, then an error (the string "Cancelled") must be thrown back into the generator. If that error is caught within the generator, execution will continue and the promise eventually resolves with the next yielded or returned value; otherwise, the promise is rejected with this error. When the generator finishes normally the resulting promise resolves with the returned value, and if the generator throws an error the promise rejects with the error.
Key Insights
- The generator yields promises; the driver code must resume the generator with the resolved value or throw into it when a promise rejects.
- Cancellation is implemented by racing the yielded promise with a “cancellation promise” that rejects when cancel() is called.
- When cancel() is invoked, if the generator is waiting on a promise, the cancellation signal propagates into the generator via generator.throw("Cancelled").
- If the cancellation error is caught within the generator, processing may resume, otherwise the main promise rejects.
- Careful management of asynchronous control flow is needed so that no further processing occurs after an uncaught cancellation.
Space and Time Complexity
Time Complexity: O(n), where n is the number of yielded promises from the generator. Space Complexity: O(n) in the worst case, due to the call stack and promise chain.
Solution
The solution revolves around driving the generator in a loop. Each iteration, we call generator.next() (or generator.throw() when an error is injected) and get a yielded promise. We then use a race (or equivalent mechanism) between the yielded promise and a cancellation promise. If the yielded promise resolves first, we feed its value back into the generator to get the next promise; if the cancellation promise “wins” (i.e. cancel() is invoked), we throw the cancellation error ("Cancelled") back into the generator. This process continues until the generator completes normally (in which case the final value is resolved) or an unhandled error occurs (leading to rejection).
To implement this, we: • Create a cancellation promise (or similar mechanism) that is triggered by the cancel function. • Use Promise.race (or similar) to wait for either the yielded promise or the cancellation. • On each resolution or rejection, resume the generator accordingly. • Return the cancel function and a promise that represents the overall computation.