ember-concurrency: Does not work with native classes

Using Ember 3.6, ember-concurrency 0.8.26

Given a component defined with ‘classic’ syntax, all is good.

import Component from '@ember/component'
import { task, timeout } from 'ember-concurrency';

export default Component.extend({
  result: null,
  doStuff: task(function*(){
    yield timeout(1000);
    this.set('result', 'done');
  })
});

If, however, we use native class syntax

import Component from '@ember/component';
import { task, timeout } from 'ember-concurrency';

export default class NativeTaskComponent extends Component {
  result = null;
  doStuff = task(function*() {
    yield timeout(1000);
    this.set('result', new Date());
  });
}

We see -task-property.js:620 Uncaught Error: It looks like you tried to perform a task via this.nameOfTask.perform(), which isn't supported. Use 'this.get('nameOfTask').perform()' instead.

I’ve spent a little time trying to figure out why this is the case and have found that when creating a TaskProperty, the function passed to super does not get executed https://github.com/machty/ember-concurrency/blob/master/addon/-task-property.js#L416-L437

export class TaskProperty extends _ComputedProperty {
  constructor(taskFn) {
    let tp;
    console.log('called before super');
    super(function(_propertyName) {
      console.log('called inside super anonymous function');
      taskFn.displayName = `${_propertyName} (task)`;
...

The transpiled output of the classic invocation is

define("dummy/components/classic-task/component", ["exports", "ember-concurrency"], function (_exports, _emberConcurrency) {
  "use strict";

  Object.defineProperty(_exports, "__esModule", {
    value: true
  });
  _exports.default = void 0;

  var _default = Ember.Component.extend({
    result: null,
    doStuff: (0, _emberConcurrency.task)(function* () {
      yield (0, _emberConcurrency.timeout)(1000);
      this.set('result', 'done');
    })
  });

  _exports.default = _default;
});

Whilst the native version is

define("dummy/components/native-task/component", ["exports", "ember-concurrency"], function (_exports, _emberConcurrency) {
  "use strict";

  Object.defineProperty(_exports, "__esModule", {
    value: true
  });
  _exports.default = void 0;

  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

  class NativeTaskComponent extends Ember.Component {
    constructor(...args) {
      super(...args);

      _defineProperty(this, "result", null);

      _defineProperty(this, "doStuff", (0, _emberConcurrency.task)(function* () {
        yield (0, _emberConcurrency.timeout)(1000);
        this.set('result', new Date());
      }));
    }

  }

  _exports.default = NativeTaskComponent;
});

So it looks like it’s caused by the way the property ends up being defined on the native class that is causing the issue.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 17 (9 by maintainers)

Commits related to this issue

Most upvoted comments

For completeness sake: In the Octane edition of Ember.js, you can create a task like this:

class MyClass {
  @task(function*() {
    // ...
  }) myTask;
}

Hmm, I’m seeing the “classic” syntax generating that error: Class constructor ComputedProperty cannot be invoked without 'new':

  setupSso: task(function* () {
    let tokenValue = this.getTokenParam();
    if (tokenValue) {
    ...

References (-task-property.js:398):

  function TaskProperty(taskFn) {
    let tp = this;
    _utils._ComputedProperty.call(this, function (_propertyName) {     // THIS LINE
      taskFn.displayName = `${_propertyName} (task)`;
      return Task.create({
        fn: tp.taskFn,

OK, perhaps it’s worth updating the docs to reflect that, as this issue is likely to come up more frequently as the community marches forward

@gabrielgrant That is correct, thanks! I updated the snippet. 😃

Pretty sure you are running into #262 and #265, which is why making sure you are on the latest version of ember-concurrency fixed it. 😊

Closing this out, since documentation has been added on using with native classes on Ember 3.10+. More specific issues w/ native classes, decorators, etc. can be addressed in separate issues as needed in the future.

I’d support adding the @task(function*() {}) syntax to the official docs, as @jenweber suggested.

We can also mention ember-concurrency-decorators as an alternative. Related: #326

Could we start by adding an example to the Task Function Syntax page? Or do we also need to cover the caveats/edge cases for it to be merged?

Example gist: https://gist.github.com/jenweber/443dac9876c7ef2b1115093cfd5d6fac

The three solutions are:

  • define task in .extend({ }) block, like
    class extends Component.extend({
      myTask: task(function*() {}),
    }) {
      // rest of the class body
    }
    
  • use defineProperty, which has performance implications
  • use ember-concurrency-decorators, for which I need to update the type definitons