Encapsulated Tasks

Normally, you define tasks by passing a generator function to task(...). Often though, you want to be able to expose additional state of the task, e.g. you might want to show the percentage progress of an uploadFile task, but unless you're using the techniques described below there's no good place to expose that data to the template other than to set some properties on the host object, but then you lose a lot of the benefits of encapsulation in the process.

In cases like these, you can use Encapsulated Tasks, which behave just like regular tasks, but with one crucial difference: the value of this within the task function points to the currently running TaskInstance, rather than the host object that the task lives on (e.g. a Component, Controller, etc). This allows for some nice patterns where all of the state produced/mutated by a task can be contained (encapsulated) within the Task itself, rather than having to live on the host object.

To create an encapsulated task, pass an object (instead of a generator function) to the task() constructor that defines a perform generator function. The object can also contain initial values for task state, as well as computed properties and anything else supported by classic Ember objects.

import { task } from 'ember-concurrency';

export default Component.extend({
  outerFoo: 123,
  regularTask: task(function * (value) {
    // this is a classic/regular ember-concurrency task,
    // which has direct access to the host object that it
    // lives on via `this`
    console.log(this.outerFoo); // => 123
    yield doSomeAsync();
    this.set('outerFoo', value);

  encapsulatedTask: task({
    innerFoo: 456,

    // this `*perform() {}` syntax is valid JavaScript shorthand
    // syntax for `perform: function * () {}`

    *perform(value) {
      // this is an encapulated task. It does NOT have
      // direct access to the host object it lives on, but rather
      // only the properties defined within the POJO passed
      // to the `task()` constructor.
      console.log(this.innerFoo); // => 456

      // `this` is the currently executing TaskInstance, so
      // you can also get classic TaskInstance properties
      // provided by ember-concurrency.
      console.log(this.get('isRunning')); // => true

      yield doSomeAsync();
      this.set('innerFoo', value);

Live Example

This example demonstrates how to use encapsulated tasks to model file uploads. It keeps all of the upload state within each TaskInstance, and uses Derived State to expose the values set within the encapsulated tasks.

Queued Uploads: 0
import { task, timeout } from 'ember-concurrency';

export default Controller.extend({
  uploadFile: task({
    progress: 0,
    url: null,

    stateText: computed('progress', function() {
      let progress = this.get('progress');
      if (progress < 49) {
        return "Just started..."
      } else if (progress < 100) {
        return "Halfway there..."
      } else {
        return "Done!"

    *perform(makeUrl) {
      this.set('url', makeUrl());

      while (this.progress < 100) {
        yield timeout(200);
        let newProgress = this.progress + Math.floor(Math.random() * 6) + 5;
        this.set('progress', Math.min(100, newProgress));

      return "(upload result data)";

  makeRandomUrl() {
    return `https://www.${randomWord()}.edu`;
  <button onclick={{perform uploadFile makeRandomUrl}}>
    Start Upload

<h5>Queued Uploads: {{uploadFile.numQueued}}</h5>

{{#with uploadFile.last as |encapsTask|}}
    Uploading to {{encapsTask.url}} ({{encapsTask.stateText}}):

{{#if uploadFile.lastSuccessful}}
  <h5 style="color: green;">
    Upload to {{uploadFile.lastSuccessful.url}}: