TypeScript and Glint

Ember Concurrency tasks play nicely with TypeScript and all of the APIs covered in these docs. Here is an example of a TypeScript component with an ember-concurrency task:

import Component from '@glimmer/component';
import { task, timeout } from 'ember-concurrency';

export default class extends Component {
  myTask = task(async (ms: number) => {
    await timeout(ms);
    return 'done!';
  });
}

Glint Template Registry

Ember Concurrency provides a template registry for using the perform, cancel-all, and task helpers within handlebars templates in Glint "loose mode". See the example below for how to include Ember Concurrency's template registry in your Glint configuration.

// e.g. types/glint.d.ts
import '@glint/environment-ember-loose';
import type EmberConcurrencyRegistry from 'ember-concurrency/template-registry';

declare module '@glint/environment-ember-loose/registry' {
  export default interface Registry
    extends EmberConcurrencyRegistry /* other addon registries */ {
    // local entries
  }
}

Ember Template Imports (.gts/.gts) Files

Here is an example of a modern .gts file in "strict mode" which imports the classic perform helper from Ember Concurrency.

Note: while you can import and use the perform helper, it is actually recommended to use the .perform() method on each task, which is internally bound to the task (similar to methods decorated with @action). One of the benefits of using the .perform() method is that it can be used with modern idiomatic patterns like using the fn helper to curry additional args when performing the task.

Pardon the lack of syntax! PR's welcome to improve our syntax highlighting!

import Component from "@glimmer/component";
import { task } from "ember-concurrency";
import perform from "ember-concurrency/helpers/perform";
import { on } from "@ember/modifier";
import { fn } from "@ember/helper";

export default class Demo extends Component {
  taskNoArgs = task(async () => {
    console.log("Look ma, no args!");
  });

  taskWithArgs = task(async (value: string) => {
    console.log(value);
  });

  <template>
    <button type="button" {{on "click" this.taskNoArgs.perform}}>
      Task with no Params (.perform method) (RECOMMENDED)
    </button>

    <button type="button" {{on "click" (perform this.taskNoArgs)}}>
      Task with no Params (with classic perform helper)
    </button>

    <button type="button" {{on "click" (fn this.taskNoArgs.perform '123')}}>
      Task with Params (currying with fn helper) (RECOMMENDED)
    </button>

    <button type="button" {{on "click" (perform this.taskWithArgs '123')}}>
      Task with Params (currying with classic perform helper)
    </button>
  </template>
}

Typing Task objects

In most cases, you don't need to provide type annotations for your task, but when you do (such as when specifying the Args of a Glimmer component), you can use the Task type:

import Component from '@glimmer/component';
import { task, timeout } from 'ember-concurrency';
import type { Task } from 'ember-concurrency';

// Define a Type task that takes a single number argument and returns a string
type MyTaskType = Task<string, [number]>;

interface Args {
  fooTask: MyTaskType;
}

export default class extends Component<Args> {
  slowlyComputeStringLength: MyTaskType = task(async (ms: number) => {
    await timeout(ms);

    const length = await this.args.fooTask.perform(ms);

    return length;
  });
}