Previous: Task Function Syntax
Next: TypeScript
Since 2.0.0, ember-concurrency uses the Stage 1 proposed decorator syntax for declaring/configuring ember-concurrency tasks on native JavaScript/TypeScript classes. This syntax was previously provided by the ember-concurrency-decorators addon, but was merged into ember-concurrency. It is still available as a seperate addon for use with ember-concurrency 1.x and earlier, or when supporting both ember-concurrency v1 and v2 in an addon using decorators.
For more context on decorators and Ember, you can read an excellent briefing on decorator syntax in modern Ember here: Coming soon in Ember Octane (Part 1): Native Classes
@task
:
turns a generator method into a task
@restartableTask
@dropTask
@keepLatestTask
@enqueueTask
@taskGroup
:
creates a task group from a property
@restartableTaskGroup
@dropTaskGroup
@keepLatestTaskGroup
@enqueueTaskGroup
@lastValue
:
alias a property to the result of a task with an optional default value
@task
import Component from '@ember/component';
import { task } from 'ember-concurrency';
export default class ExampleComponent extends Component {
@task
*doStuff() {
// ...
}
// and then elsewhere
executeTheTask() {
// `doStuff` is still a `Task` object that can be `.perform()`ed
this.doStuff.perform();
console.log(this.doStuff.isRunning);
}
}
You can also pass further options to the task decorator:
@task({
maxConcurrency: 3,
restartable: true
})
*doStuff() {
// ...
}
You can also use task lifecycle event hooks in your tasks:
@task({ on: 'didInsertElement' })
*doStuff() {
// ...
}
For your convenience, there are extra decorators for all concurrency modifiers:
Shorthand | Equivalent |
---|---|
@restartableTask |
@task({ restartable: true }) |
@dropTask |
@task({ drop: true }) |
@keepLatestTask |
@task({ keepLatest: true }) |
@enqueueTask |
@task({ enqueue: true }) |
You can still pass further options to these decorators, like:
@restartableTask({ maxConcurrency: 3 })
*doStuff() {
// ...
}
Encapsulated Tasks 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.
import Component from '@ember/component';
import { task } from 'ember-concurrency';
export default class ExampleComponent extends Component {
@task
doStuff = {
privateState: 123,
*perform() {
// ...
}
};
// and then elsewhere
executeTheTask() {
// `doStuff` is still a `Task` object that can be `.perform()`ed
this.doStuff.perform();
console.log(this.doStuff.isRunning);
}
}
Unfortunately, encapsulated tasks have challenges with TypeScript. See the TypeScript section for more details.
@taskGroup
import Component from '@ember/component';
import { task, taskGroup } from 'ember-concurrency';
export default class ExampleComponent extends Component {
@taskGroup
someTaskGroup;
@task({ group: 'someTaskGroup' })
*doStuff() {
// ...
}
@task({ group: 'someTaskGroup' })
*doOtherStuff() {
// ...
}
// and then elsewhere
executeTheTask() {
// `doStuff` is still a `Task `object that can be `.perform()`ed
this.doStuff.perform();
// `someTaskGroup` is still a `TaskGroup` object
console.log(this.someTaskGroup.isRunning);
}
}
You can also pass further options to the task group decorator:
@taskGroup({
maxConcurrency: 3,
drop: true
}) someTaskGroup;
As for @task
, there are extra decorators for all
concurrency modifiers:
Shorthand | Equivalent |
---|---|
@restartableTaskGroup |
@taskGroup({ restartable: true }) |
@dropTaskGroup |
@taskGroup({ drop: true }) |
@keepLatestTaskGroup |
@taskGroup({ keepLatest: true }) |
@enqueueTaskGroup |
@taskGroup({ enqueue: true }) |
You can still pass further options to these decorators, like:
@dropTaskGroup({ maxConcurrency: 3 }) someTaskGroup;
@lastValue
This decorator allows you to alias a property to the result of a task. You can also provide a default value to use before the task has completed.
import Component from '@ember/component';
import { task } from 'ember-concurrency';
import { lastValue } from 'ember-concurrency';
export default class ExampleComponent extends Component {
@task
*someTask() {
// ...
}
@lastValue('someTask')
someTaskValue;
@lastValue('someTask')
someTaskValueWithDefault = 'A default value';
}
You can use this package with TypeScript, but unfortunately decorators cannot yet change the type signature of the decorated element. This is why you may get type errors like:
import { task } from 'ember-concurrency';
export default class Foo {
@task
*doStuff(this: Foo) {
// ...
}
executeTheTask() {
// @ts-ignore
this.doStuff.perform();
}
}
error TS2339: Property 'perform' does not exist on type '(this: Foo) => Generator:<never, void, unknown>'.
See the documentation on TypeScript for details and workarounds.
Previous: Task Function Syntax
Next: TypeScript