Previous: Cancelation
Next: Child Tasks
When you yield
a promise or async function call,
your task function will pause execution until one of three things happens:
The Task Function Syntax docs
demonstrates how you can use standard JavaScript try/catch
blocks
to catch exceptions thrown when you yield a rejecting promise, but
what about cancelation? Are cancelations considered exceptions/errors, or something else?
In ember-concurrency, cancelation is considered a third kind of "completion"
(the other two being a successful return from a function, and throwing an exception
from a function).
Specifically, this means that if a task is canceled while it is paused on a
yield, the task will essentially return from that point,
it will skip any catch(e) {}
blocks it is in, but it will
execute any finally {}
blocks. The benefit of this behavior is that:
finally
blocks will always run and can be used for cleanup logic *catch
blocks (which you'd annoyingly have to do
if cancelation were considered just another type of error).
* While finally
blocks are nice for cleanup logic, make
sure you're leveraging the power of
Task Modifiers and .isRunning / .isIdle
task properties as much as possible so that you're not manually re-implementing
a lot of the implicit state that ember-concurrency provides you for free, e.g.
you should avoid manually toggling the visibility of a loading spinner within
a task if you could accomplish the same thing using the .isRunning
property on a task.
Both of the buttons below will (re)start myTask
when clicked.
If you click the buttons quickly, it will cause the currently running task
to cancel from the yield
where it is paused. Notice how
cancelations don't increment the numErrors
property because
cancelations skip the catch
block.
<button {{on "click" (perform this.myTask false)}} type="button"> Run to Completion </button> <button {{on "click" (perform this.myTask true)}} type="button"> Throw an Error </button> <ul> <li>Task State: {{this.myTask.state}}</li> <li>Completions: {{this.numCompletions}}</li> <li>Errors: {{this.numErrors}}</li> <li>Finally block runs: {{this.numFinallys}}</li> </ul>
export default class ErrorVsCancelationController extends Controller { numCompletions = 0; numErrors = 0; numFinallys = 0; @restartableTask *myTask(doError) { try { yield timeout(1000); if (doError) { throw new Error('Boom'); } } catch (e) { this.incrementProperty('numErrors'); } finally { this.incrementProperty('numFinallys'); } this.incrementProperty('numCompletions'); } }
Previous: Cancelation
Next: Child Tasks