ember-concurrency perform tasks
ember-concurrency is a popular Ember addon for managing asynchronous tasks using generator functions ("tasks") that can be performed, canceled, and tracked for state. In Ember, the perform
helper is used in templates to invoke these tasks, making it simple to handle async actions like data fetching or user events with robust state management (e.g., isRunning, lastSuccessful).
When migrating from Ember to React, there is no direct equivalent we've created for ember-concurrency tasks or the perform
helper. Instead, async logic is typically handled via JavaScript async functions, React hooks (like useState
, useEffect
), or libraries such as SWR. The migration involves:
- Moving generator-based tasks to async functions or Promises.
- Replacing the
perform
helper with explicit function calls (often as event handlers in JSX).
Ember
In Ember, asynchronous actions are managed using ember-concurrency tasks. Tasks are generator functions decorated with @task
, which provide built-in state (such as isRunning) and cancellation. The perform
helper in templates allows you to trigger these tasks directly from user interactions, such as button clicks, while automatically handling task state and preventing race conditions.
import Component from '@ember/component';
import { task } from 'ember-concurrency';
import { inject as service } from '@ember/service';
import classic from 'ember-classic-decorator';
@classic
export default class IceCreamComponent extends Component {
@service store;
@task
selectFlavor = function* (flavor) {
const ice = yield this.store.createRecord('ice-cream-flavoflav', {
flavorSlug: flavor.flavoflavSlug,
});
return ice;
}
}
<button
onclick={{fn (perform this.selectFlavor) "minty chocolate chip"}}
>
We Want MINT
</button>
React
In React, asynchronous logic is typically handled using async functions, React hooks, or external libraries such as SWR. You manually manage loading and error state. Then, you invoke async actions via event handlers (e.g., onClick
). This allows flexibility but also requires more explicit state management than in Ember.
By extracting the template logic, you can maintain the same functionality with backend logic in Ember. In this example, we are using a React component inside of an Ember component for the on-click functionality by passing an action.
import Component from '@ember/component';
import { task } from 'ember-concurrency';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import classic from 'ember-classic-decorator';
@classic
export default class IceCreamComponent extends Component {
@service store;
@task
selectFlavor = function* (flavor) {
const ice = yield this.store.createRecord('ice-cream-flavoflav', {
flavorSlug: flavor.flavoflavSlug,
});
return ice;
}
@action
performTaskSelectFlavor(flavor) {
return this.selectFlavor.perform(flavor);
}
}
<ReactIceCreamButton
@performTaskSelectFlavor={{this.performTaskSelectFlavor}}
/>
export default function ReactIceCreamButton({
performTaskSelectFlavor
}) {
return (
<button onClick={() => performTaskSelectFlavor("minty chocolate chip")}>
We Want MINT
</button>
)
};