Cancellation

ember-concurrency tasks can be canceled either explicitly (by calling one of the cancel methods on a task or task instance) or implicitly (based on how the task is configured, or because the task's host object was destroyed).

How ember-concurrency cancels task functions

You may be aware that async functions in JavaScript are not cancellable, i.e. if you call an async function, you can't externally cause that async function to stop running (unless you're making explicit use of cancel tokens or some other mechanism).

ember-concurrency's task() API is built around async arrow functions, so you might be wondering, how does e-c get around this limitation? The answer is that behind the scenes, ember-concurrency uses a Babel transform to convert your async arrow function into a Generator Function, which are able to be cancelled.

Implicit Cancelation via Task Modifiers

Generally speaking, it is much better to configure your tasks properly (via Task Modifiers) such that they'll be implicitly/automatically canceled at the right time, but there are still some cases where explicit cancelation is the only option.

Explicit Cancelation

There are two ways to explicitly cancel a task:

  1. Call task.cancelAll() on the Task object — this will cancel all running or enqueued Task Instances for that task.
  2. Call taskInstance.cancel() on a Task Instance (the object returned from a prior call to task.perform())

Example

Running tasks: 0

Tip: You can also use the .numRunning property to get the current number of running task instances for a given task, e.g. {{myTask.numRunning}}: 0

<h5>Running tasks: {{this.count}}</h5>

<button type="button" {{on "click" this.performTask}}>Perform Task</button>
{{#if this.count}}
  <button type="button" {{on "click" this.cancelAll}}>Cancel All</button>
{{/if}}
{{#if this.mostRecent.isRunning}}
  <button type="button" {{on "click" this.cancelMostRecent}}>Cancel Most Recent</button>
{{/if}}
export default class CancelationController extends Controller {
  count = 0;
  mostRecent = null;

  myTask = task(async () => {
    try {
      this.incrementProperty('count');
      await forever;
    } finally {
      // finally blocks always get called,
      // even when the task is being canceled
      this.decrementProperty('count');
    }
  });

  @action
  performTask() {
    let task = this.myTask;
    let taskInstance = task.perform();
    this.set('mostRecent', taskInstance);
  }

  @action
  cancelAll() {
    this.myTask.cancelAll();
  }

  @action
  cancelMostRecent() {
    this.mostRecent.cancel();
  }
}