### 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}} {{/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; } }); } ``` ```