ember-concurrency-async
v1.0.0
Published
Async task functions for ember-concurrency
Downloads
8,934
Maintainers
Keywords
Readme
ember-concurrency-async
This addon introduces an alternative syntax for ember-concurrency tasks that uses async methods instead of generator methods:
import Component from '@glimmer/component';
import { task } from 'ember-concurrency';
class FooComponent extends Component {
@task async myTask(foo, bar) {
let resolvedFoo = await foo;
let resolvedBar = await this.process(bar);
return resolvedFoo + resolvedBar;
}
async process(bar) {
// ...
}
}
The main advantage of the async method syntax over the generator method syntax is that it works better with the TypeScript compiler and things powered by it, such as the JavaScript language server in Visual Studio Code.
Due to limitations in TypeScript's understanding of generator functions, it is not possible to express the realtionship between the left and right hand side of a yield expression. In the case of ember-concurrency task functions, we would like to inform the compiler that yielding a value "returns" its resolved value. If yield is a function, this is how we would type it:
type Resolved<T> = T extends PromiseLike<infer R> ? R : T;
function yield<T>(yieldable: T): Resolved<T>;
Unfortunately, this is not possible in TypeScript today. However, the await
keyword in async functions have exactly the semantics we want, and TypeScript
already understands that natively. This addon allows you to take advantage of
that by authoring tasks in the async methods syntax, but transforms the async
methods into the generator methods expected by ember-concurrency's runtime
code using a babel plugin.
The example from earlier will be transformed into this:
import Component from '@glimmer/component';
import { task } from 'ember-concurrency';
export default class FooComponent extends Component {
@task *myTask(foo, bar) {
let resolvedFoo = yield foo;
let resolvedBar = yield this.process(bar);
return resolvedFoo + resolvedBar;
}
async process(bar) {
// ...
}
}
Note that only the async method annotated by the @task
decorator was
rewritten into a generator method.
Compatibility
- Ember.js v3.12 or above
- Ember CLI v2.13 or above
- Node.js v10 or above
This addon requires ember-concurrency v2.0.0-rc.1 or higher. If you're using an earlier version of ember-concurrency and/or using ember-concurrency-decorators, you may use ember-concurrency-async < 1.0.0.
Installation
ember install ember-concurrency-async
Usage
Any async methods annotated with one of the task decorators, that is
@task
, @restartableTask
, @dropTask
, @keepLatestTask
or @enqueueTask
,
will be transformed into a generator function.
Note that this transformation happens at build time and only works if it can statically determine an async method is an ember-concurrency task. If does so by looking at the decorators applied to an async method and try to determine if they matches one of the known task decorator imports. For example, these would work:
import { task } from 'ember-concurrency';
class Foo {
@task({ restartable: true }) async foo() {
// ...
}
}
import { restartableTask } from 'ember-concurrency';
class Foo {
@restartableTask async foo() {
// ...
}
}
import { enqueueTask as enqueued } from 'ember-concurrency';
class Foo {
@enqueued async foo() {
// ...
}
}
import * as ec from 'ember-concurrency';
class Foo {
@ec.dropTask async foo() {
// ...
}
}
However, these won't:
import { restartableTask } from 'ember-concurrency';
const restartable = restartableTask;
class Foo {
@restartable async foo() {
// ...
}
}
import { enqueueTask, dropTask } from 'ember-concurrency';
function enqueueOrDrop() {
if (Ember.testing) {
return dropTask();
} else {
return enqueueTask();
}
}
class Foo {
@enqueueOrDrop async foo() {
// ...
}
}
import * as ec from 'ember-concurrency';
class Foo {
@ec[Ember.testing ? 'dropTask' : 'enqueueTask'] async foo() {
// ...
}
}
If you encountered use cases that you believe should work but doesn't, please open an issue.
TypeScript
ember-concurrency 1.2 and above comes with type definitions. If you are using
TypeScript (or the JavaScript Language Server powered by TypeScript), you may
want to add the following import in types/<app name>/index.d.ts
:
import 'ember-concurrency-async';
This augments ember-concurrency's type definitions to add support for async task functions. See index.d.ts.
If you are using ember-concurrency-ts, you may also need to add:
import 'ember-concurrency-ts/async';
Usage with taskFor
at assignment
ember-concurrency-ts
provides a taskFor
utility function for casting task functions to the correct
type. taskFor
can be
used at assignment
and is compatible with ember-concurrency-async
:
import { task } from 'ember-concurrency';
import { taskFor } from 'ember-concurrency-ts';
class Foo {
bar: Promise<void>;
@task
myTask = taskFor(async function(this: Foo) {
await this.bar;
});
}
This also works with async arrow functions, eliminating the need to type
this
:
import { task } from 'ember-concurrency';
import { taskFor } from 'ember-concurrency-ts';
class Foo {
bar: Promise<void>;
@task
myTask = taskFor(async () => {
await this.bar;
});
}
Note that async arrow functions are transformed to non-arrow generator
functions (arrow generator functions
have been proposed
but no Babel plugin exists for them at this time). The this
context is
bound, however, by the @task
decorator, so this
inside the async function
will still refer to the containing class at runtime.
Contributing
See the Contributing guide for details.
License
This project is licensed under the MIT License.