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