### Install ember-concurrency
Source: https://context7.com/machty/ember-concurrency/llms.txt
Install the ember-concurrency addon using the ember install command.
```bash
ember install ember-concurrency
```
--------------------------------
### Install Dependencies
Source: https://github.com/machty/ember-concurrency/blob/master/packages/test-app/README.md
Installs the necessary project dependencies. Run this after cloning the repository.
```bash
npm install
```
--------------------------------
### Install Dependencies
Source: https://github.com/machty/ember-concurrency/blob/master/CONTRIBUTING.md
Installs project dependencies using pnpm. Ensure you have pnpm installed globally.
```bash
pnpm i
```
--------------------------------
### Start Development Server
Source: https://github.com/machty/ember-concurrency/blob/master/CONTRIBUTING.md
Starts the Ember development server for local testing and development. Visit http://localhost:4200 to see your changes.
```bash
ember server
```
--------------------------------
### Using a Task in a Template
Source: https://context7.com/machty/ember-concurrency/llms.txt
Example of how to use a task in an Ember template to trigger the task's perform method on input and display results or loading state.
```handlebars
{{! search.hbs }}
{{#if this.searchTask.isRunning}}
Searching…
{{else if this.searchTask.last.value}}
{{#each this.searchTask.last.value as |result|}}
{{result.title}}
{{/each}}
{{/if}}
```
--------------------------------
### Build Application
Source: https://github.com/machty/ember-concurrency/blob/master/packages/test-app/README.md
Builds the Ember application. Use the --environment flag to specify production build.
```bash
ember build
```
```bash
ember build --environment production
```
--------------------------------
### `task.perform(...args)` — Run a Task
Source: https://context7.com/machty/ember-concurrency/llms.txt
The `perform()` method creates and immediately schedules a new `TaskInstance`. It returns the instance synchronously, which is a promise-like object with cancelation-aware `.then()`, `.catch()`, and `.finally()` methods.
```APIDOC
## `task.perform(...args)` — Run a Task
`perform()` creates and immediately schedules a new `TaskInstance`, returning it synchronously. The instance is a promise-like object with cancelation-aware `.then()` / `.catch()` / `.finally()`.
### Example Usage
```javascript
import Component from '@glimmer/component';
import { task, timeout } from 'ember-concurrency';
import { didCancel } from 'ember-concurrency';
export default class SaveComponent extends Component {
saveTask = task({ drop: true }, async (payload) => {
await timeout(100);
const res = await fetch('/api/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error(`Save failed: ${res.status}`);
return res.json();
});
async handleSave(payload) {
try {
const result = await this.saveTask.perform(payload);
console.log('Saved:', result);
} catch (e) {
if (didCancel(e)) {
// Task was dropped — a save is already in progress, ignore silently
return;
}
// Real error — surface to user
console.error('Save error:', e.message);
}
}
}
```
```
--------------------------------
### Run Ember Compatibility Tests
Source: https://github.com/machty/ember-concurrency/blob/master/CONTRIBUTING.md
Runs compatibility tests against multiple Ember versions using `ember try:each`. This ensures the addon works across different Ember releases.
```bash
pnpm test:ember-compatibility
```
--------------------------------
### `TaskInstance` — Inspect Running Instances
Source: https://context7.com/machty/ember-concurrency/llms.txt
Each `task.perform()` call returns a `TaskInstance`. This instance exposes reactive state properties and can be canceled individually. It provides properties like `isRunning`, `isFinished`, `isSuccessful`, `isCanceled`, `isDropped`, `value`, `error`, and `state`.
```APIDOC
## `TaskInstance` — Inspect Running Instances
Every `task.perform()` call returns a `TaskInstance`. It exposes reactive state properties and can be canceled individually.
### State Properties
- `.isRunning` — `true` while executing
- `.isFinished` — `true` after completion (success, error, or cancel)
- `.isSuccessful` — `true` if resolved without error/cancel
- `.isCanceled` — `true` if explicitly canceled or dropped
- `.isDropped` — `true` if canceled before it ever started
- `.value` — resolved return value (after `isSuccessful`)
- `.error` — thrown error (after `isError`)
- `.state` — `'running' | 'waiting' | 'finished' | 'canceled' | 'dropped'`
### Example Usage
```javascript
import Component from '@glimmer/component';
import { task, timeout } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking';
export default class UploadComponent extends Component {
@tracked currentInstance = null;
uploadTask = task(async (file) => {
const form = new FormData();
form.append('file', file);
const res = await fetch('/api/upload', { method: 'POST', body: form });
return res.json();
});
async startUpload(file) {
this.currentInstance = this.uploadTask.perform(file);
await this.currentInstance;
console.log('Uploaded:', this.currentInstance.value);
}
cancelUpload() {
// Cancel a specific instance (not all instances)
this.currentInstance?.cancel('User aborted upload');
}
}
```
```
--------------------------------
### Build Ember Addon
Source: https://github.com/machty/ember-concurrency/blob/master/CONTRIBUTING.md
Builds the Ember addon for production. This command is typically run before releasing.
```bash
ember build
```
--------------------------------
### Configure CI for Ember-Concurrency version testing
Source: https://github.com/machty/ember-concurrency/blob/master/UPGRADING-2.x.md
Include ember-try scenarios for ember-concurrency 1.x and 2.x in your CI configuration to ensure smooth transitions.
```yaml
# .travis.yml or equivalent for GitHub Actions, CircleCI, etc.
jobs:
include:
# ... other configurations
- env: EMBER_TRY_SCENARIO=ember-canary
- env: EMBER_TRY_SCENARIO=ember-concurrency-1.x
- env: EMBER_TRY_SCENARIO=ember-concurrency-2.x
```
--------------------------------
### Run a Task with `task.perform()`
Source: https://context7.com/machty/ember-concurrency/llms.txt
Use `task.perform()` to create and schedule a new `TaskInstance`. The instance is a promise-like object that can be awaited and has cancelation-aware methods. Handle potential cancellations using `didCancel()`.
```javascript
import Component from '@glimmer/component';
import { task, timeout } from 'ember-concurrency';
import { didCancel } from 'ember-concurrency';
export default class SaveComponent extends Component {
saveTask = task({ drop: true }, async (payload) => {
await timeout(100);
const res = await fetch('/api/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error(`Save failed: ${res.status}`);
return res.json();
});
async handleSave(payload) {
try {
const result = await this.saveTask.perform(payload);
console.log('Saved:', result);
} catch (e) {
if (didCancel(e)) {
// Task was dropped — a save is already in progress, ignore silently
return;
}
// Real error — surface to user
console.error('Save error:', e.message);
}
}
}
```
--------------------------------
### Release New Version
Source: https://github.com/machty/ember-concurrency/blob/master/CONTRIBUTING.md
Builds the package and then uses release-it to manage the versioning and publishing process to NPM. Ensure you are in the `packages/ember-concurrency` directory.
```bash
cd packages/ember-concurrency
pnpm build
npx release-it
```
--------------------------------
### Configure ember-try scenarios for Ember-Concurrency versions
Source: https://github.com/machty/ember-concurrency/blob/master/UPGRADING-2.x.md
Set up ember-try scenarios in `config/ember-try.js` to test compatibility with both ember-concurrency 1.x and 2.x.
```javascript
// config/ember-try.js
{
// ...
{
name: 'ember-concurrency-1.x',
npm: {
dependencies: {
'ember-concurrency': '^1.3.0'
}
}
},
{
name: 'ember-concurrency-2.x',
npm: {
dependencies: {
'ember-concurrency': '^2.0.0-rc.1'
}
}
},
// ...
}
```
--------------------------------
### Inspect Running Task Instances
Source: https://context7.com/machty/ember-concurrency/llms.txt
Each `task.perform()` call returns a `TaskInstance` which provides reactive state properties like `isRunning`, `isFinished`, `value`, and `error`. You can also cancel individual instances using their `.cancel()` method.
```javascript
import Component from '@glimmer/component';
import { task, timeout } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking';
export default class UploadComponent extends Component {
@tracked currentInstance = null;
uploadTask = task(async (file) => {
const form = new FormData();
form.append('file', file);
const res = await fetch('/api/upload', { method: 'POST', body: form });
return res.json();
});
async startUpload(file) {
this.currentInstance = this.uploadTask.perform(file);
// TaskInstance state properties:
// .isRunning — true while executing
// .isFinished — true after completion (success, error, or cancel)
// .isSuccessful — true if resolved without error/cancel
// .isCanceled — true if explicitly canceled or dropped
// .isDropped — true if canceled before it ever started
// .value — resolved return value (after isSuccessful)
// .error — thrown error (after isError)
// .state — 'running' | 'waiting' | 'finished' | 'canceled' | 'dropped'
await this.currentInstance;
console.log('Uploaded:', this.currentInstance.value);
}
cancelUpload() {
// Cancel a specific instance (not all instances)
this.currentInstance?.cancel('User aborted upload');
}
}
```
--------------------------------
### Run Tasks in Parallel with `all`
Source: https://context7.com/machty/ember-concurrency/llms.txt
The `all` utility runs multiple task instances concurrently. If the parent task is canceled or any child task fails, all other running tasks are canceled.
```javascript
import Component from '@glimmer/component';
import { task, all, timeout } from 'ember-concurrency';
export default class DashboardComponent extends Component {
fetchUserTask = task(async (id) => { return fetch(`/api/users/${id}`).then(r => r.json()); });
fetchOrdersTask = task(async (id) => { return fetch(`/api/orders?userId=${id}`).then(r => r.json()); });
fetchStatsTask = task(async (id) => { return fetch(`/api/stats/${id}`).then(r => r.json()); });
loadDashboardTask = task({ drop: true }, async (userId) => {
// All three requests run in parallel; if any fail or this task is canceled,
// the others are automatically canceled too.
const [user, orders, stats] = await all([
this.fetchUserTask.perform(userId),
this.fetchOrdersTask.perform(userId),
this.fetchStatsTask.perform(userId),
]);
this.user = user;
this.orders = orders;
this.stats = stats;
});
}
```
--------------------------------
### Run Standard Ember Tests
Source: https://github.com/machty/ember-concurrency/blob/master/CONTRIBUTING.md
Executes the standard Ember test suite. Use the `--server` flag to run tests in watch mode.
```bash
ember test
```
```bash
ember test --server
```
--------------------------------
### Manage Race Conditions with `race`
Source: https://context7.com/machty/ember-concurrency/llms.txt
The `race` utility runs multiple tasks and resolves as soon as the first one settles. All other tasks are canceled immediately upon resolution or rejection of the first.
```javascript
import Component from '@glimmer/component';
import { task, race, timeout } from 'ember-concurrency';
export default class TimeoutComponent extends Component {
fetchTask = task(async (url) => fetch(url).then(r => r.json()));
timeoutTask = task(async () => {
await timeout(5000);
throw new Error('Request timed out after 5s');
});
fetchWithTimeoutTask = task({ drop: true }, async (url) => {
// Whichever finishes first wins; the other is automatically canceled
return race([
this.fetchTask.perform(url),
this.timeoutTask.perform(),
]);
});
}
```
--------------------------------
### Importing and Using Tasks and Timeout
Source: https://github.com/machty/ember-concurrency/blob/master/packages/test-app/API.md
Demonstrates the basic import of `task` and `timeout` from 'ember-concurrency' and their usage within a component to create a looping task that updates a tracked property.
```javascript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { task, timeout } from 'ember-concurrency';
export default class MyComponent extends Component {
@tracked num;
constructor() {
super(...arguments);
this.loopingTask.perform();
}
@task *loopingTask() {
while (true) {
this.num = Math.random();
yield timeout(100);
}
}
});
```
--------------------------------
### Parallel Tasks with Named Results using `hash`
Source: https://context7.com/machty/ember-concurrency/llms.txt
The `hash` utility is similar to `all` but accepts an object of task instances or promises. It returns an object with the same keys and their settled values.
```javascript
import Component from '@glimmer/component';
import { task, hash } from 'ember-concurrency';
export default class ProfileComponent extends Component {
fetchUserTask = task(async (id) => fetch(`/api/users/${id}`).then(r => r.json()));
fetchPostsTask = task(async (id) => fetch(`/api/posts?author=${id}`).then(r => r.json()));
loadProfileTask = task({ restartable: true }, async (userId) => {
const { user, posts } = await hash({
user: this.fetchUserTask.perform(userId),
posts: this.fetchPostsTask.perform(userId),
});
this.user = user;
this.posts = posts;
});
}
```
--------------------------------
### Configure package.json for Ember-Concurrency 1.x and 2.x
Source: https://github.com/machty/ember-concurrency/blob/master/UPGRADING-2.x.md
Specify compatible versions of ember-concurrency in your package.json to support both 1.x and 2.x.
```json
{
// ...
"dependencies": { // Or use `peerDependencies` if appropriate
// ...
"ember-concurrency": "^1.0.0 || ^2.0.0-rc.1",
// ...
},
// ...
}
```
--------------------------------
### `task.cancelAll(options?)` — Cancel All Instances
Source: https://context7.com/machty/ember-concurrency/llms.txt
Cancels all currently running or queued `TaskInstance`s. You can optionally provide a reason string and/or reset derived state.
```APIDOC
## `task.cancelAll(options?)` — Cancel All Instances
Cancels all currently running or queued `TaskInstance`s. Optionally provide a reason string and/or reset derived state.
### Example Usage
```javascript
import Route from '@ember/routing/route';
import { task, timeout } from 'ember-concurrency';
export default class DetailRoute extends Route {
pollTask = task({ restartable: true }, async (id) => {
try {
while (true) {
await timeout(5000);
await fetch(`/api/items/${id}`);
}
} finally {
// cleanup runs whether the task finishes naturally or is canceled
console.log(`Stopped polling item ${id}`);
}
});
setupController(controller, model) {
super.setupController(...arguments);
this.pollTask.perform(model.id);
}
resetController() {
super.resetController(...arguments);
// Cancel polling when leaving the route — fires the `finally` block above
this.pollTask.cancelAll({ reason: 'Left detail route', resetState: true });
}
}
```
```
--------------------------------
### Task Modifiers: Shorthand Constructors
Source: https://context7.com/machty/ember-concurrency/llms.txt
Provides shorthand constructor functions for common task modifiers like restartableTask, dropTask, enqueueTask, and keepLatestTask.
```javascript
import { restartableTask, dropTask, enqueueTask, keepLatestTask } from 'ember-concurrency';
export default class MyComponent extends Component {
searchTask = restartableTask(async (q) => { /* ... */ });
submitTask = dropTask(async (data) => { /* ... */ });
saveTask = enqueueTask(async (r) => { /* ... */ });
refreshTask = keepLatestTask(async () => { /* ... */ });
}
```
--------------------------------
### Await Task Cancelation with `cancelAll`
Source: https://github.com/machty/ember-concurrency/blob/master/UPGRADING-2.x.md
When calling `cancelAll` with `{ resetState: true }`, the state reset happens on cancelation finalization. It is important to `await` calls to `cancelAll` to ensure the cancelation is complete before proceeding.
```javascript
@dropTask *myTask() {
while(1) {
console.log("hello!");
yield timeout(1000);
}
}
@task *restartMyTask() {
// Note the `yield`. This could also be an `await` or `then`, if done
// outside of tasks
yield this.myTask.cancelAll();
// Without being able to `yield` on `cancelAll` above, the cancelation
// wouldn't be guaranteed to have taken place before this is called,
// resulting in `perform` no-oping and dropping the task and myTask would
// not restart
this.myTask.perform();
}
```
--------------------------------
### Invoke Tasks with `{{perform}}` Helper
Source: https://github.com/machty/ember-concurrency/blob/master/UPGRADING-2.x.md
Direct use of tasks with the `{{action}}` helper is removed. Convert existing uses to `{{perform}}` directly or by wrapping the task in `(perform)` before passing to `{{action}}` or `{{fn}}`.
```handlebars
```
```handlebars
{{!-- Any of these --}}
```
--------------------------------
### Define a Basic Unbounded Task
Source: https://context7.com/machty/ember-concurrency/llms.txt
Define a task using the task() function. This task is unbounded, meaning multiple instances can run simultaneously. It includes a timeout for debouncing and fetches search results.
```javascript
import Component from '@glimmer/component';
import { task, timeout } from 'ember-concurrency';
export default class SearchComponent extends Component {
// Basic unbounded task — multiple instances can run simultaneously
searchTask = task(async (query) => {
await timeout(300); // debounce
const results = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
if (!results.ok) throw new Error('Search failed');
return results.json();
});
}
```
--------------------------------
### Handle All Task Outcomes with `allSettled`
Source: https://context7.com/machty/ember-concurrency/llms.txt
Similar to `all`, `allSettled` runs tasks in parallel but waits for all to complete, regardless of success or failure. It returns an array of settlement objects.
```javascript
import Component from '@glimmer/component';
import { task, allSettled } from 'ember-concurrency';
export default class BulkComponent extends Component {
processItemTask = task(async (item) => {
const res = await fetch(`/api/items/${item.id}`, { method: 'POST' });
if (!res.ok) throw new Error(`Failed: ${item.id}`);
return res.json();
});
bulkProcessTask = task({ drop: true }, async (items) => {
const instances = items.map((item) => this.processItemTask.perform(item));
const results = await allSettled(instances);
const succeeded = results.filter((r) => r.state === 'fulfilled');
const failed = results.filter((r) => r.state === 'rejected');
console.log(`${succeeded.length} succeeded, ${failed.length} failed`);
});
}
```
--------------------------------
### Task/cancelation-aware Promise Helpers
Source: https://github.com/machty/ember-concurrency/blob/master/packages/test-app/API.md
Task/cancelation-aware variants of Promise helpers that support cancelation.
```APIDOC
## Task/cancelation-aware variants of Promise helpers
These helpers are just like their Promise/RSVP equivalents, but with
the added behavior that they support cancelation and can hence be
used in conjunction with Tasks / TaskInstances.
- [all](global.html#all)
- [allSettled](global.html#allSettled)
- [hash](global.html#hash)
- [hashSettled](global.html#hashSettled)
- [race](global.html#race)
```
--------------------------------
### Lint Code
Source: https://github.com/machty/ember-concurrency/blob/master/packages/test-app/README.md
Runs the code linter to check for style and potential errors. Use --fix to automatically correct issues.
```bash
npm run lint
```
```bash
npm run lint:fix
```
--------------------------------
### Custom Yieldable
Source: https://context7.com/machty/ember-concurrency/llms.txt
Extend the `Yieldable` base class to create reusable, cancelation-aware async primitives. The `onYield(state)` method must return a disposer function for cleanup when the task is canceled.
```APIDOC
## Custom `Yieldable` — Build Your Own Awaitable Primitive
Extend the `Yieldable` base class to create reusable, cancelation-aware async primitives. `onYield(state)` must return a disposer function for cleanup when the task is canceled.
```js
import Component from '@glimmer/component';
import { task, Yieldable } from 'ember-concurrency';
// Custom yieldable wrapping requestIdleCallback
class IdleCallbackYieldable extends Yieldable {
onYield(state) {
const id = requestIdleCallback((deadline) => {
state.next(deadline); // resume the task with the IdleDeadline
});
return () => cancelIdleCallback(id); // cleanup if task is canceled
}
}
const idleCallback = () => new IdleCallbackYieldable();
export default class IdleComponent extends Component {
backgroundTask = task(async () => {
while (true) {
const deadline = await idleCallback();
if (deadline.timeRemaining() > 10) {
this.processNextChunk();
}
}
});
processNextChunk() {
// Do incremental work here
}
}
```
```
--------------------------------
### Cancel All Task Instances with `task.cancelAll()`
Source: https://context7.com/machty/ember-concurrency/llms.txt
Call `task.cancelAll()` to cancel all running or queued `TaskInstance`s. This is useful for cleanup, such as when leaving a route. You can optionally provide a reason and reset derived state.
```javascript
import Route from '@ember/routing/route';
import { task, timeout } from 'ember-concurrency';
export default class DetailRoute extends Route {
pollTask = task({ restartable: true }, async (id) => {
try {
while (true) {
await timeout(5000);
await fetch(`/api/items/${id}`);
}
} finally {
// cleanup runs whether the task finishes naturally or is canceled
console.log(`Stopped polling item ${id}`);
}
});
setupController(controller, model) {
super.setupController(...arguments);
this.pollTask.perform(model.id);
}
resetController() {
super.resetController(...arguments);
// Cancel polling when leaving the route — fires the `finally` block above
this.pollTask.cancelAll({ reason: 'Left detail route', resetState: true });
}
}
```
--------------------------------
### Miscellaneous API
Source: https://github.com/machty/ember-concurrency/blob/master/packages/test-app/API.md
Various utility functions for ember-concurrency.
```APIDOC
## Misc
- [animationFrame](global.html#animationFrame)
- [didCancel](global.html#didCancel)
- [forever](global.html#forever)
- [rawTimeout](global.html#rawTimeout)
- [waitForEvent](global.html#waitForEvent)
- [waitForQueue](global.html#waitForQueue)
```
--------------------------------
### rawTimeout(ms)
Source: https://context7.com/machty/ember-concurrency/llms.txt
Pauses a task using the native `setTimeout` API. Unlike `timeout`, test helpers will not wait for `rawTimeout` to complete. Use this for background polling or animations that should not block test assertions.
```APIDOC
## rawTimeout(ms) — Native `setTimeout` Delay
Pauses a task using the native `setTimeout` API. Unlike `timeout`, test helpers will **not** wait for `rawTimeout` to complete — use this for background polling or animations that should not block test assertions.
```js
import Component from '@glimmer/component';
import { task, rawTimeout } from 'ember-concurrency';
export default class HeartbeatComponent extends Component {
heartbeatTask = task(async () => {
while (true) {
await fetch('/api/heartbeat', { method: 'POST' });
// Tests won't wait for this — keeps test suite fast
await rawTimeout(30_000);
}
});
constructor(...args) {
super(...args);
this.heartbeatTask.perform();
}
}
```
```
--------------------------------
### hashSettled(taskInstances)
Source: https://context7.com/machty/ember-concurrency/llms.txt
A cancelation-aware version of RSVP.hashSettled that accepts and returns a named object of task instances. It resolves with an object where each value is a settlement descriptor ({ state, value } or { state, reason }).
```APIDOC
## `hashSettled(taskInstances)` — Cancelation-Aware `RSVP.hashSettled`
Like `allSettled()` but accepts and returns a named object. Each value is a `{ state, value }` or `{ state, reason }` settlement descriptor.
```js
import Component from '@glimmer/component';
import { task, hashSettled } from 'ember-concurrency';
export default class MetricsComponent extends Component {
fetchRevenueTask = task(async () => fetch('/api/metrics/revenue').then(r => r.json()));
fetchUsersTask = task(async () => fetch('/api/metrics/users').then(r => r.json()));
loadMetricsTask = task({ drop: true }, async () => {
const results = await hashSettled({
revenue: this.fetchRevenueTask.perform(),
users: this.fetchUsersTask.perform(),
});
if (results.revenue.state === 'fulfilled') this.revenue = results.revenue.value;
if (results.users.state === 'fulfilled') this.users = results.users.value;
// Partial failures don't block the others
});
}
```
```
--------------------------------
### Configure Babel Transform for Ember App
Source: https://github.com/machty/ember-concurrency/blob/master/README.md
Configure the Babel transform for ember-concurrency in an Ember App's `ember-cli-build.js` file. Ensure the `async-arrow-task-transform` plugin is included.
```javascript
// in app ember-cli-build.js
const app = new EmberApp(defaults, {
// ...
babel: {
plugins: [
// ... any other plugins
require.resolve("ember-concurrency/async-arrow-task-transform"),
// NOTE: put any code coverage plugins last, after the transform.
],
}
});
```
--------------------------------
### Triggering Tasks with Arguments in Ember Templates
Source: https://github.com/machty/ember-concurrency/blob/master/packages/test-app/snippets/ts/template-import-example.txt
For tasks that accept arguments, use `fn` helper to curry arguments with the `.perform` method. The classic `perform` helper can also be used with arguments.
```typescript
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 {
taskWithArgs = task(async (value: string) => {
console.log(value);
});
}
```
--------------------------------
### Configure Babel Transform for ember-concurrency
Source: https://context7.com/machty/ember-concurrency/llms.txt
Configure the required Babel transform in ember-cli-build.js to enable async arrow task transformations.
```javascript
// ember-cli-build.js
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
module.exports = function (defaults) {
const app = new EmberApp(defaults, {
babel: {
plugins: [
require.resolve('ember-concurrency/async-arrow-task-transform'),
// NOTE: put code coverage plugins AFTER this transform
],
},
});
return app.toTree();
};
```
--------------------------------
### Use `rawTimeout` for Native `setTimeout` Delays
Source: https://context7.com/machty/ember-concurrency/llms.txt
Pauses a task using the native `setTimeout` API. Test helpers will not wait for this, making it ideal for background polling or animations that should not block test assertions.
```javascript
import Component from '@glimmer/component';
import { task, rawTimeout } from 'ember-concurrency';
export default class HeartbeatComponent extends Component {
heartbeatTask = task(async () => {
while (true) {
await fetch('/api/heartbeat', { method: 'POST' });
// Tests won't wait for this — keeps test suite fast
await rawTimeout(30_000);
}
});
constructor(...args) {
super(...args);
this.heartbeatTask.perform();
}
}
```
--------------------------------
### Triggering Tasks with No Arguments in Ember Templates
Source: https://github.com/machty/ember-concurrency/blob/master/packages/test-app/snippets/ts/template-import-example.txt
Use the `.perform` method directly for triggering tasks without arguments. Alternatively, the `perform` helper can be used for the same purpose.
```typescript
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!");
});
}
```
--------------------------------
### Configure Babel Transform for V2 Addon
Source: https://github.com/machty/ember-concurrency/blob/master/README.md
Configure the Babel transform for ember-concurrency in a V2 Ember addon's `babel.config.json` file. This enables the async-arrow task transform for the addon.
```json
{
"plugins": [
// ... any other plugins
"ember-concurrency/async-arrow-task-transform"
]
}
```
--------------------------------
### forever()
Source: https://context7.com/machty/ember-concurrency/llms.txt
Suspends a task indefinitely until it is canceled. This is useful for keeping a task alive through route transitions or other scenarios where it should persist until explicitly stopped.
```APIDOC
## forever() — Pause Indefinitely Until Canceled
Suspends a task indefinitely until it is canceled (e.g., host object destroyed, `cancelAll()` called, or a `restartable` modifier fires). Useful for keeping a task "alive" through route transitions.
```js
import Component from '@glimmer/component';
import { task, forever } from 'ember-concurrency';
import { service } from '@ember/service';
export default class ButtonComponent extends Component {
@service router;
activeDuringTransitionTask = task({ drop: true }, async () => {
this.isActive = true;
try {
// Start the transition, then park here until this component is destroyed
await this.router.transitionTo('next-route');
await forever();
} finally {
this.isActive = false;
}
});
}
```
```
--------------------------------
### Configure Babel Transform for V1 Addon
Source: https://github.com/machty/ember-concurrency/blob/master/README.md
Configure the Babel transform for ember-concurrency within a V1 Ember addon's `index.js` file. This ensures the async-arrow task transform is applied.
```javascript
// in V1 addon index.js
// ...
options: {
babel: {
plugins: [
require.resolve('ember-concurrency/async-arrow-task-transform'),
],
},
},
```
--------------------------------
### Cancelation-Aware `hashSettled` for Multiple Tasks
Source: https://context7.com/machty/ember-concurrency/llms.txt
Use `hashSettled` to concurrently run multiple tasks and wait for all of them to settle, similar to `Promise.allSettled` but for task instances. This is useful for fetching related data where partial failures should not block the entire operation.
```javascript
import Component from '@glimmer/component';
import { task, hashSettled } from 'ember-concurrency';
export default class MetricsComponent extends Component {
fetchRevenueTask = task(async () => fetch('/api/metrics/revenue').then(r => r.json()));
fetchUsersTask = task(async () => fetch('/api/metrics/users').then(r => r.json()));
loadMetricsTask = task({ drop: true }, async () => {
const results = await hashSettled({
revenue: this.fetchRevenueTask.perform(),
users: this.fetchUsersTask.perform(),
});
if (results.revenue.state === 'fulfilled') this.revenue = results.revenue.value;
if (results.users.state === 'fulfilled') this.users = results.users.value;
// Partial failures don't block the others
});
}
```
--------------------------------
### animationFrame()
Source: https://context7.com/machty/ember-concurrency/llms.txt
Pauses a task until after the next `requestAnimationFrame` tick. The pending frame is automatically canceled if the task is canceled.
```APIDOC
## animationFrame() — Pause Until Next Animation Frame
Pauses a task until after the next `requestAnimationFrame` tick. Automatically cancels the pending frame if the task is canceled.
```js
import Component from '@glimmer/component';
import { task, animationFrame } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking';
export default class AnimationComponent extends Component {
@tracked progress = 0;
animateTask = task({ restartable: true }, async (targetValue) => {
const start = this.progress;
const startTime = performance.now();
const duration = 500; // ms
while (true) {
await animationFrame();
const elapsed = performance.now() - startTime;
const t = Math.min(elapsed / duration, 1);
this.progress = start + (targetValue - start) * t; // linear interpolation
if (t >= 1) break;
}
});
}
```
```
--------------------------------
### timeout(ms)
Source: https://context7.com/machty/ember-concurrency/llms.txt
Pauses a task for a specified number of milliseconds, scheduled via the Ember runloop. Test helpers will wait for this to complete, making it ideal for use in tests.
```APIDOC
## timeout(ms) — Ember Runloop-Aware Delay
Pauses a task for `ms` milliseconds, scheduled via the Ember runloop. Test helpers (`await settled()`) will wait for this to complete — prefer `timeout` in tests.
```js
import Component from '@glimmer/component';
import { task, timeout } from 'ember-concurrency';
export default class NotificationComponent extends Component {
showNotificationTask = task({ restartable: true }, async (message) => {
this.notification = message;
await timeout(3000); // automatically waits in tests
this.notification = null;
});
notify(message) {
// Calling perform() again restarts the timer (restartable)
this.showNotificationTask.perform(message);
}
}
```
```
--------------------------------
### Task Modifiers API
Source: https://github.com/machty/ember-concurrency/blob/master/packages/test-app/API.md
Functions for registering and looking up user-defined Task Modifiers.
```APIDOC
## Task Modifier API
These functions provide the ability to register and lookup registered user-defined
Task Modifiers.
- [getModifier](global.html#getModifier)
- [hasModifier](global.html#hasModifier)
- [registerModifier](global.html#registerModifier)
```
--------------------------------
### Task Modifiers: restartable, drop, enqueue, keepLatest
Source: https://context7.com/machty/ember-concurrency/llms.txt
Demonstrates different task modifiers for controlling concurrency policies: restartable (cancel and restart), drop (ignore new calls), enqueue (run sequentially), and keepLatest (drop all but the latest).
```javascript
import Component from '@glimmer/component';
import { task, timeout } from 'ember-concurrency';
export default class TypeaheadComponent extends Component {
// restartable: cancel any running instance and start fresh
searchTask = task({ restartable: true }, async (query) => {
await timeout(250);
return fetch(`/api/search?q=${query}`).then((r) => r.json());
});
// drop: ignore new .perform() calls while already running
submitTask = task({ drop: true }, async (formData) => {
await fetch('/api/submit', { method: 'POST', body: JSON.stringify(formData) });
});
// enqueue: queue up instances, run them one at a time
saveTask = task({ enqueue: true }, async (record) => {
await fetch(`/api/records/${record.id}`, { method: 'PUT', body: JSON.stringify(record) });
});
// keepLatest: drop all but the most recently queued instance
refreshTask = task({ keepLatest: true }, async () => {
await fetch('/api/data').then((r) => r.json());
});
// maxConcurrency: allow up to 3 simultaneous instances (works with any policy)
uploadTask = task({ enqueue: true, maxConcurrency: 3 }, async (file) => {
const form = new FormData();
form.append('file', file);
await fetch('/api/upload', { method: 'POST', body: form });
});
}
```
--------------------------------
### Use `waitForQueue` to Pause Until an Ember Runloop Queue
Source: https://context7.com/machty/ember-concurrency/llms.txt
Pauses a task until a specific Ember runloop queue is reached, such as `'afterRender'`. This is useful for reading DOM measurements after a render cycle.
```javascript
import Component from '@glimmer/component';
import { task, waitForQueue } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking';
export default class MeasureComponent extends Component {
@tracked height = 0;
measureTask = task(async () => {
this.showContent = true;
// Wait for Glimmer to flush the render
await waitForQueue('afterRender');
// Now the DOM is updated — safe to measure
this.height = this.element.getBoundingClientRect().height;
});
}
```
--------------------------------
### Custom `Yieldable` for `requestIdleCallback`
Source: https://context7.com/machty/ember-concurrency/llms.txt
Create a custom `Yieldable` to integrate browser-native asynchronous APIs like `requestIdleCallback` into Ember Concurrency tasks. Ensure cleanup by returning a disposer function from `onYield` to cancel the callback when the task is canceled.
```javascript
import Component from '@glimmer/component';
import { task, Yieldable } from 'ember-concurrency';
// Custom yieldable wrapping requestIdleCallback
class IdleCallbackYieldable extends Yieldable {
onYield(state) {
const id = requestIdleCallback((deadline) => {
state.next(deadline); // resume the task with the IdleDeadline
});
return () => cancelIdleCallback(id); // cleanup if task is canceled
}
}
const idleCallback = () => new IdleCallbackYieldable();
export default class IdleComponent extends Component {
backgroundTask = task(async () => {
while (true) {
const deadline = await idleCallback();
if (deadline.timeRemaining() > 10) {
this.processNextChunk();
}
}
});
processNextChunk() {
// Do incremental work here
}
}
```
--------------------------------
### Detect Task Cancellation with `didCancel(error)`
Source: https://context7.com/machty/ember-concurrency/llms.txt
Use `didCancel(error)` within a `catch` block to reliably determine if an error was caused by task cancellation. This allows you to differentiate between expected cancellations and actual errors.
```javascript
import Component from '@glimmer/component';
import { task, timeout, didCancel } from 'ember-concurrency';
export default class PollComponent extends Component {
fetchTask = task({ restartable: true }, async (url) => {
await timeout(1000);
const res = await fetch(url);
return res.json();
});
async refresh(url) {
try {
const data = await this.fetchTask.perform(url);
this.data = data;
} catch (e) {
if (didCancel(e)) {
// Previous call was restarted — not an error condition
return;
}
this.errorMessage = e.message;
}
}
}
```
--------------------------------
### Use `forever` to Pause Indefinitely Until Canceled
Source: https://context7.com/machty/ember-concurrency/llms.txt
Suspends a task indefinitely until it is canceled. This is useful for keeping a task 'alive' through route transitions or until a component is destroyed.
```javascript
import Component from '@glimmer/component';
import { task, forever } from 'ember-concurrency';
import { service } from '@ember/service';
export default class ButtonComponent extends Component {
@service router;
activeDuringTransitionTask = task({ drop: true }, async () => {
this.isActive = true;
try {
// Start the transition, then park here until this component is destroyed
await this.router.transitionTo('next-route');
await forever();
} finally {
this.isActive = false;
}
});
}
```
--------------------------------
### Poll Until Property Changes with Timeout
Source: https://context7.com/machty/ember-concurrency/llms.txt
Use `timeout` for polling instead of the deprecated `waitForProperty`. This approach repeatedly checks a condition with a delay.
```javascript
import Component from '@glimmer/component';
import { task, waitForProperty, timeout } from 'ember-concurrency';
export default class SyncComponent extends Component {
// Recommended polling alternative:
pollUntilReadyTask = task(async () => {
while (!this.args.service.isReady) {
await timeout(200);
}
await this.doWork.perform();
});
// Legacy waitForProperty usage (deprecated):
legacySyncTask = task(async () => {
// Resumes when this.args.service.isReady === true
await waitForProperty(this.args.service, 'isReady', true);
console.log('Service is ready!');
});
}
```
--------------------------------
### Use `animationFrame` to Pause Until Next Animation Frame
Source: https://context7.com/machty/ember-concurrency/llms.txt
Pauses a task until after the next `requestAnimationFrame` tick. The pending frame is automatically canceled if the task is canceled.
```javascript
import Component from '@glimmer/component';
import { task, animationFrame } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking';
export default class AnimationComponent extends Component {
@tracked progress = 0;
animateTask = task({ restartable: true }, async (targetValue) => {
const start = this.progress;
const startTime = performance.now();
const duration = 500; // ms
while (true) {
await animationFrame();
const elapsed = performance.now() - startTime;
const t = Math.min(elapsed / duration, 1);
this.progress = start + (targetValue - start) * t; // linear interpolation
if (t >= 1) break;
}
});
}
```
--------------------------------
### waitForQueue(queueName)
Source: https://context7.com/machty/ember-concurrency/llms.txt
Pauses a task until a specific Ember runloop queue is reached (e.g., `'afterRender'`). This is useful for reading DOM measurements after a render cycle has completed.
```APIDOC
## waitForQueue(queueName) — Pause Until an Ember Runloop Queue
Pauses a task until a specific Ember runloop queue is reached (e.g., `'afterRender'`). Useful for reading DOM measurements after a render cycle.
```js
import Component from '@glimmer/component';
import { task, waitForQueue } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking';
export default class MeasureComponent extends Component {
@tracked height = 0;
measureTask = task(async () => {
this.showContent = true;
// Wait for Glimmer to flush the render
await waitForQueue('afterRender');
// Now the DOM is updated — safe to measure
this.height = this.element.getBoundingClientRect().height;
});
}
```
```
--------------------------------
### Use `timeout` for Runloop-Aware Delays
Source: https://context7.com/machty/ember-concurrency/llms.txt
Pauses a task for a specified duration, scheduled via the Ember runloop. Test helpers will wait for this to complete, making it suitable for tests.
```javascript
import Component from '@glimmer/component';
import { task, timeout } from 'ember-concurrency';
export default class NotificationComponent extends Component {
showNotificationTask = task({ restartable: true }, async (message) => {
this.notification = message;
await timeout(3000); // automatically waits in tests
this.notification = null;
});
notify(message) {
// Calling perform() again restarts the timer (restartable)
this.showNotificationTask.perform(message);
}
}
```
--------------------------------
### `didCancel(error)` — Detect Task Cancellation
Source: https://context7.com/machty/ember-concurrency/llms.txt
The `didCancel(error)` function returns `true` if the caught error is a `TaskCancelation`. This allows you to safely distinguish between task cancellation and actual errors when treating a `TaskInstance` as a promise.
```APIDOC
## `didCancel(error)` — Detect Task Cancellation
Returns `true` if the caught error is a `TaskCancelation`, allowing you to safely distinguish cancellation from real errors when treating a `TaskInstance` as a promise.
### Example Usage
```javascript
import Component from '@glimmer/component';
import { task, timeout, didCancel } from 'ember-concurrency';
export default class PollComponent extends Component {
fetchTask = task({ restartable: true }, async (url) => {
await timeout(1000);
const res = await fetch(url);
return res.json();
});
async refresh(url) {
try {
const data = await this.fetchTask.perform(url);
this.data = data;
} catch (e) {
if (didCancel(e)) {
// Previous call was restarted — not an error condition
return;
}
this.errorMessage = e.message;
}
}
}
```
```
--------------------------------
### Migrate Decorator Imports in ember-concurrency
Source: https://github.com/machty/ember-concurrency/blob/master/UPGRADING-2.x.md
Update your import statements to use 'ember-concurrency' instead of 'ember-concurrency-decorators' when migrating to version 2.0.0.
```diff
- import { restartableTask, task } from 'ember-concurrency-decorators';
+ import { restartableTask, task } from 'ember-concurrency';
```
--------------------------------
### waitForEvent(object, eventName)
Source: https://context7.com/machty/ember-concurrency/llms.txt
Pauses a task until a named event fires on a DOM `EventTarget` or Ember Evented object. The event listener is automatically removed when the task is canceled.
```APIDOC
## waitForEvent(object, eventName) — Pause Until an Event Fires
Pauses a task until a named event fires on a DOM `EventTarget` or Ember Evented object. Automatically removes the event listener when the task is canceled.
```js
import Component from '@glimmer/component';
import { task, waitForEvent, timeout } from 'ember-concurrency';
export default class DragComponent extends Component {
dragTask = task(async (element) => {
// Wait for mousedown to begin the drag
const startEvent = await waitForEvent(element, 'mousedown');
let { clientX: startX, clientY: startY } = startEvent;
while (true) {
// Race: either mousemove (continue drag) or mouseup (end drag)
const moveEvent = await waitForEvent(document, 'mousemove');
element.style.transform =
`translate(${moveEvent.clientX - startX}px, ${moveEvent.clientY - startY}px)`;
// Check for release — the next waitForEvent will be for mousemove
const upEvent = await waitForEvent(document, 'mouseup');
if (upEvent) break;
}
});
}
```
```