FAQ & Fact Sheet
Why am I seeing TaskCancelation errors in my logs?
See this link.
Does ember-concurrency work with older browsers?
In general, yes, but they're not officially supported or tested on, so your
mileage may vary.
The ES6 Generator Function syntax
that ember-concurrency relies on can be automatically transpiled using Babel
and regenerator. You may
need to experiment with your
ember-cli-babel settings to include the regenerator runtime and proper
transpilation for your needs.
ember-concurrency v2.0.0 uses
WeakMap for implementing decorators
and encapsulated tasks, so certain older browsers may require polyfills to use
Encapsulated tasks also use
Proxy, which may not be polyfillable, so this
feature may not be available.
How do ember-concurrency Tasks compare to...
Promises and Async functions?
Async functions are just syntax sugar for Promises. Promises aren't cancelable,
and as of November 2020, there is no active TC39 specification under development
for adding cancelation to promises. Tasks, in contrast, are cancelable.
The design of Promises is such that once a Promise has been created, it's not
possible to externally reach in and resolve/reject a Promise. This constraint
encourages a clear, unidirectional, structured architecture for building
asynchronous chains of logic.
Like Promises, the return/reject value of an EC Task cannot be externally
set / overridden; in other words, once a Task has been performed, there's
no way to externally force it to return/reject early, with the exception of
cancelation. When you
.cancel() a task instance, the task
will "return" from wherever it is currently paused (e.g. at a
Presently, there is no API to "delay" the cancelation of a Task once a cancel
has been requested, but this functionality might be added to future APIs.
Both Observable subscriptions and Task instances are cancelable. Observables
can be canceled implicitly when all subscribers have unsubscribed, or explicitly
.dispose() (though this approach is frowned up in favor
of explicitly specifying when an observable should terminate -- e.g. via
Tasks can be canceled implicitly (via 1. host object destruction, 2. the
restartable task modifier) or explicitly (via
.cancel() / .cancelAll()), though,
like Observables, the implicit approach is preferable.
Multiple / Single Values
Observables can emit multiple values over time. Tasks don't really "emit" values,
.set() values on the object the task lives on so that those
values can be easily displayed in the template. Tasks also expose the value / error
returned from the task function (see Derived State),
which are preferable to
.set()s where possible.
The degree to which an abstraction "composes" depends highly on the problems
it is trying to solve and the constraints therein. Observables as a low-level
primitive aren't designed to solve specific problems but rather to be as flexible
and composable as possible so that developers can build almost anything on top of
them to model just about any domain logic under the sun. So, in a sense,
Observables can be considered more composable (compositional?) than Tasks,
which, by the time they exist, are bound to the lifetime of the object
they live on, which constrains how and where Tasks can be used, but this tradeoff
is deliberate and enables some of the patterns described below.
Marble Diagrams vs Block Diagrams
The behavior of Observables and their various combinators can be
visualized by Marble Diagrams, where each
marble represents a discrete event emitted. Tasks don't emit multiple values,
and instead make a greater emphasis on the concept of task/object lifespans, hence
are more easily visualized as
possibly overlapping blocks over time.
Warning: highly subjective. ember-concurrency tasks are designed to "feel" like
an Ember-y API; among other design goals, it should be relatively easy for a
newcomer to the code base come along and understand the flow and various steps
taken from the start to the end of the task. It is the contention of @machty,
who is biased but also well-versed in both observables and tasks,
that understanding the flow of an ember-concurrency
task is much "easier" than untangling a mess of Observable combinators for
an observable that does the same amount of work and exposes the same amount
of derived state as an ember-concurrency task (and @machty has written plenty
of apps with Observables). This clarity is due to 1) the pausable/resumable nature
of the generator function syntax that ember-concurrency tasks use,
2) the fact that tasks expose a lot of commonplace derivable state that you'd
otherwise have to split out / filter / merge yourself using observables
.isIdle / .isRunning / .numRunning) and 3)
it's often easier to model domain state as bindable values rather than