ember-concurrency
Cancelable, restartable async task primitives for Ember.js that replace manual promise and flag juggling with declarative concurrency control.
Repository Health
Technical Analysis
ember-concurrency is an Ember addon that gives components, routes, and services a first-class task() primitive for managing asynchronous work. Instead of manually tracking loading flags, canceling in-flight requests, and guarding against out-of-order responses, you wrap generator or async-arrow functions in task() and apply declarative modifiers like restartable, drop, enqueue, and keepLatest to control exactly how concurrent invocations behave.
Under the hood, tasks are built on a scheduler and buffer-policy system that tracks running, queued, and canceled TaskInstances, exposing derived state (isRunning, last, lastSuccessful, performCount, etc.) that templates and computed properties can consume directly. Since v4 it ships as a TypeScript-authored Embroider v2 addon with full type declarations, a Babel transform for async-arrow task syntax, and built-in yieldables (timeout, animationFrame, waitForEvent) for expressing cancelable async workflows without hand-rolled promise bookkeeping.
What You Get
- task() primitive - wraps a generator or async-arrow function into a cancelable Task with automatic destructor-based cleanup tied to the host object’s lifecycle
- Concurrency modifiers - restartable, drop, enqueue, keepLatest, and maxConcurrency control how overlapping perform() calls are scheduled without manual bookkeeping
- Derived task state - isRunning, isIdle, last, lastSuccessful, lastErrored, performCount, and more, computed automatically and consumable in templates or JS
- Built-in yieldables - timeout, animationFrame, waitForEvent, waitForQueue, and rawTimeout give cancelable pauses inside a task without leaking timers or listeners
- TypeScript-first types - full .d.ts declarations and Glint template-type support for typed task definitions and helper invocations
Common Use Cases
- Debounced search input - a component performs a restartable search task on every keystroke so only the latest request’s response is applied
- Save button that ignores double-clicks - a drop-policy task on a form’s save action prevents duplicate submissions while a save is in flight
- Polling a background job’s status - a task loops with await timeout(ms) between checks and is automatically canceled when the component is destroyed
- Sequential queue of user actions - an enqueue-policy task processes actions like reordering list items one at a time in the order they were triggered
Under The Hood
Architecture The engine uses a layered, generator-based state-machine model. The public task() API (src/-private/task-public-api.js) creates a TaskFactory that applies declarative modifiers (restartable/drop/enqueue/keepLatest/maxConcurrency) through a MODIFIER_REGISTRY, then instantiates a Task bound to a host context, registering lifecycle cleanup via Ember’s destroyable API. Each perform() call produces a TaskInstance whose generator stepping is delegated to a TaskInstanceExecutor, which drives a GeneratorState wrapper one step at a time using an index/ticket mechanism to invalidate stale resumes, alongside a Yieldable protocol that lets built-in and user-defined async primitives register disposer callbacks for cancellation-safe cleanup. Concurrency scheduling is delegated to a separate Scheduler plus pluggable SchedulerPolicy classes, decoupling “what happens on concurrent perform()” from the execution engine itself, which makes the buffer-policy layer swappable without touching the generator engine that every Task funnels through.
Tech Stack The addon is TypeScript-authored and built as an Embroider v2 addon using @embroider/addon-dev and @embroider/addon-shim, bundled with Rollup rather than the legacy ember-cli broccoli pipeline, and ships a custom Babel plugin (async-arrow-task-transform.js) that rewrites task(async () => {…}) arrow syntax into generator functions at build time. Runtime dependencies are minimal and Babel-internal only; the actual task engine has no dependency on RSVP or jQuery and integrates directly with Ember’s runloop, destroyable, and debug modules. A separate test-app workspace is a full Ember application (ember-source, ember-cli) used purely as a test harness and interactive documentation site.
Code Quality Testing lives in the sibling test-app workspace and is extensive: QUnit unit tests cover every scheduler policy (drop, restartable, enqueue, keepLatest, unbounded), task/task-instance/task-state behavior, cancelable-promise helpers, wait-for utilities, error handling, and self-cancel-loop detection, plus .gts integration tests for the perform/task template helpers, acceptance tests, and a dedicated types-tests package exercising the TypeScript declarations. CI runs linting (ESLint, Prettier, ember-template-lint, Glint type checks) followed by cross-browser QUnit runs and a scheduled floating-dependencies job. Cancellation is modeled explicitly and distinctly from errors via a dedicated cancelation-name check and typed CancelRequest kinds (lifespan-end, parent-cancel, yieldable-cancel), and unhandled task rejections are reported rather than swallowed. Naming is consistent throughout and core files carry substantial inline documentation explaining non-obvious control flow.
API Design The task() pattern turns an async generator or async-arrow function into a first-class object with rich derived state (isRunning, isIdle, last, lastSuccessful, performCount) that eliminates manual flag bookkeeping, and buffer-policy modifiers are expressed as plain boolean options on task() itself, keeping call sites declarative and one-line rather than requiring separate cancellation-token plumbing. Getting started requires wiring a Babel transform into the consuming app’s build to unlock the ergonomic async-arrow syntax, a modest amount of setup friction that is offset by an extensive interactive documentation site covering installation, tutorials, cancelation, concurrency, TypeScript usage, testing/debugging, and version upgrades.
Used by 2 apps in this directory
Fleetbase
Logistics
Modular open-source logistics and supply chain operating system
Ghost
CMS · Blogging
Open source headless Node.js CMS for professional publishing, paid memberships, and newsletters with a fully owned audience.