
Product
Announcing Socket Fix 2.0
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
ember-concurrency-ts
Advanced tools
TypeScript utilities for ember-concurrency.
This is how you would typically write ember-concurrency tasks in Octane:
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { task, TaskGenerator, timeout } from 'ember-concurrency';
export default class extends Component {
@task *myTask(ms: number): TaskGenerator<string> {
yield timeout(ms);
return 'done!';
}
@action performTask() {
if (this.myTask.isRunning) {
return;
}
this.myTask.perform(1000).then(value => {
console.log(value.toUpperCase());
});
}
}
Since we are using native classes in Octane, TypeScript have an easier time understanding and following our code. Normally, this is a good thing, but in the case of ember-concurrency, it ends up getting a bit in the way.
ember-concurrency's API was designed with Class Ember in mind, where it could
decorate a property or method and replace it with a different type in the
.extend()
hook.
This is not allowed using TypeScript's decorators. Since myTask
is defined
using as the generator method syntax, and since methods do not have a
.perform()
method on them, calling this.myTask.perform()
will result in a
type error, even though it will work at runtime.
We could work around this by type casting the method, such as
(this.myTask as any as Task<string, number>)
, but doing this everywhere is
quite verbose and error-prone.
Instead, this addon provides some TypeScript-specific utility functions to encapsulate the type cast transparently. See the Usage section for details.
ember install ember-concurrency-ts
Optionally, if using ember-concurrency-async, add the following to
types/<app name>/index.d.ts
:
import 'ember-concurrency-async';
import 'ember-concurrency-ts/async';
taskFor
The taskFor
utility function allows the code example from above to type
check:
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { task, TaskGenerator, timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { taskFor } from 'ember-concurrency-ts';
export default class extends Component {
@task *myTask(ms: number): TaskGenerator<string> {
yield timeout(ms);
return 'done!';
}
@action performTask() {
if (taskFor(this.myTask).isRunning) {
return;
}
taskFor(this.myTask).perform(1000).then(value => {
console.log(value.toUpperCase());
});
}
}
Instead of accessing the task directly, wrapping it in the taskFor
utility
function will allow TypeScript to understand what we are trying to accomplish.
If this becomes repetitive, you may extract it into a variable or getter, and
the code will still type check:
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { TaskGenerator, timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { taskFor } from 'ember-concurrency-ts';
export default class extends Component {
@task *myTask(ms: number): TaskGenerator<string> {
yield timeout(ms);
return 'done!';
}
@action performTask() {
let myTask = taskFor(this.myTask);
if (myTask.isRunning) {
return;
}
myTask.perform(1000).then(value => {
console.log(value.toUpperCase());
});
}
}
Note that everything on the task is type-inferred from the method definition.
Based on the return type of *myTask
, TypeScript knows that myTask.value
is
string | undefined
. Likewise, it knows that myTask.perform()
takes the same
arguments as *myTask
. Passing the wrong arguments will be a type error. It
also knows that the value
promise callback parameter is a string
.
taskFor
The taskFor
utility function can also be used at assignment:
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { TaskGenerator, timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { taskFor } from 'ember-concurrency-ts';
export default class extends Component {
@task myTask = taskFor(function*(ms: number): TaskGenerator<string> {
yield timeout(ms);
return 'done!';
});
@action performTask() {
if (this.myTask.isRunning) {
return;
}
this.myTask.perform(1000).then(value => {
console.log(value.toUpperCase());
});
}
}
This allows you to access the task directly without using taskFor
and perform
. The one
caveat here is that the this
type must be asserted if you are referencing this
in your task:
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { TaskGenerator, timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { taskFor } from 'ember-concurrency-ts';
export default class MyComponent extends Component {
returnVal = 'done';
@task myTask = taskFor(function*(this: MyComponent, ms: number): TaskGenerator<string> {
yield timeout(ms);
return this.returnVal;
});
@action performTask() {
if (this.myTask.isRunning) {
return;
}
this.myTask.perform(1000).then(value => {
console.log(value.toUpperCase());
});
}
}
perform
As a convenience, this addon also provide a perform
utility function as a
shorthand for myTask(...).perform(...)
:
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { TaskGenerator, timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { perform } from 'ember-concurrency-ts';
export default class extends Component {
@task *myTask(ms: number): TaskGenerator<string> {
yield timeout(ms);
return 'done!';
}
@action performTask() {
perform(this.myTask, 1000).then(value => {
console.log(value.toUpperCase());
});
}
}
Just like taskFor
, it infers the type information from *myTask
, type checks
the arguments as has the right return type, etc.
ember-concurrency-async
This addon can be used together with ember-concurrency-async, see the Installation section for additional instructions.
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { timeout } from 'ember-concurrency';
import { task } from 'ember-concurrency-decorators';
import { taskFor, perform } from 'ember-concurrency-ts';
export default class extends Component {
@task async myTask(ms: number): Promise<string> {
await timeout(ms);
return 'done!';
}
@action performTask() {
if (taskFor(this.myTask).isRunning) {
return;
}
perform(this.myTask, 1000).then(value => {
console.log(value.toUpperCase());
});
}
}
Under-the-hood, these utility functions are just implemented as unsafe type
casts. For example, the examples will still type check if
the @task
decorator is omitted (so this.myTask
is just a regular generator
or async method), but you will get an error at runtime.
See the Contributing guide for details.
This project is licensed under the MIT License.
FAQs
TypeScript utilities for ember-concurrency
The npm package ember-concurrency-ts receives a total of 5,533 weekly downloads. As such, ember-concurrency-ts popularity was classified as popular.
We found that ember-concurrency-ts demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
Security News
Socket CEO Feross Aboukhadijeh joins Risky Business Weekly to unpack recent npm phishing attacks, their limited impact, and the risks if attackers get smarter.
Product
Socket’s new Tier 1 Reachability filters out up to 80% of irrelevant CVEs, so security teams can focus on the vulnerabilities that matter.