Previous: Lifecycle Events
Starting with ember-concurrency v2.2.0, public Task Modifier APIs have been added to allow for custom task modifiers to be registered from third-party addons and application code to provide behavior for specific situations not suited to inclusion within ember-concurrency's core itself.
Task modifiers have been a concept built-in to `ember-concurrency` since the beginning. However, until `0.7.19` they were only specifyable within `ember-concurrency` internals, and not extendable by users. `0.7.19` added the ability to specify new modifiers as prototype extensions on `TaskProperty`. Unfortunately, `TaskProperty` is inherently tied to Ember internals and is not used when using decorators, and using prototype extensions does not make clear what modifiers exist, so the `getModifier`, `hasModifier`, and `registerModifier` APIs were introduced to provide a way to manage modifiers and make them discoverable.
Let's say we want to build a benchmarking modifier to help us get a sense for how long our tasks are running.
// app/task-modifiers/benchmark.js import { registerModifier } from 'ember-concurrency'; function benchmarkModifier(taskFactory, option) { if (!window && !window.performance) { return; } if (option) { let taskDefinition = taskFactory.taskDefinition; let benchmarkedDefinition = function* (...args) { let taskName = taskFactory.name; let namespace = `ember-concurrency.${taskName}`; window.performance.mark(`${namespace}.start`); try { yield* taskDefinition(...args); window.performance.measure( `${namespace}.success`, `${namespace}.start` ); } catch (e) { window.performance.measure(`${namespace}.error`, `${namespace}.start`); throw e; } finally { window.performance.measure( `${namespace}.runtime`, `${namespace}.start` ); } }; taskFactory.setTaskDefinition(benchmarkedDefinition); } } registerModifier('benchmark', benchmarkModifier); export default benchmarkModifier;
Now that we have a new modifier defined, we can apply it to any tasks that we wish and have it apply the behavior we built. Let's see it in action!
<button {{on "click" (perform this.doWork)}} type="button"> Benchmark task </button> {{#if this.doWork.isRunning}} Running benchmark... {{/if}} <ol> {{#each this.perfEntries as |entry|}} <li>Start time: {{entry.startTime}}ms after page-load; duration = {{entry.duration}}ms</li> {{/each}} </ol>
import { task, timeout } from 'ember-concurrency'; // registerModifer is called in the module defining the modifier, // so we're really just importing it here for the side-effect. This is mostly for // terseness in this illustration. You may want to seperate defining the modifier // and registering it with registerModifier, and be explicit about where you // register (e.g. addon, library, or app initialization) import 'dummy/task-modifiers/benchmark'; let performance = typeof window !== 'undefined' && window.performance ? window.performance : { getEntriesByName() {} }; export default class TaskModifiersController extends Controller { @task({ drop: true, benchmark: true }) *doWork() { yield timeout(20000 * Math.random()); } @computed('doWork.isRunning') get perfEntries() { if (this.doWork.isRunning) { return []; } else { return performance.getEntriesByName( 'ember-concurrency.doWork.runtime', 'measure' ); } } }
The behavior and implementation of task modifier APIs are defined further in
the Task Modifiers RFC.
Additionally, the API for TaskFactory
is documented here.
Previous: Lifecycle Events