Skip to content
View from the base of a building looking up at the curves of the balconies

A Day in the Life of an Ember Concurrency Task…

Media Suite's itinerant American developer explores one of the nuances of ember-concurrency.

If you don’t know ember-concurrency…Recommended reading: This article by Alex Matchneer. Play with these examples.

Right, for those who want to crack on, let’s get into it!

Not Everything Is as It Seems

Nominally, the .drop()  task modifier in ember-concurrency indicates that no more than one instance of a task may ever be running once at a time. However, you might have missed this crucial sentence from a page deep down in the Route Tasks page of the documentation:

“ember-concurrency tasks are scoped to the lifetime of the object they live on, so if that object is destroyed, all of the tasks attached to it are cancelled.”

The important takeaway here is the first bit: “ember-concurrency tasks are scoped to the lifetime of the object they live on”. i.e. ember-concurrency tasks are scoped to object instances, not classes.

As you’ll see below, you can actually have multiple instances of a task running simultaneously, despite having applied a modifier (drop, enqueue, restartable, keepLatest) to prevent that from happening. This applies to all of the task modifiers, but we’ll just stick with drop in our examples to keep things simple.

Take the following component (view the full twiddle example here):

import {task, timeout} from 'ember-concurrency'
export default Ember.Component.extend({
someTask: task(function * (id) {
console.log('Start', id)
yield timeout(1000)
console.log('Done', id)
}).drop()
})
<button onclick={{perform someTask componentName}}>Button {{componentName}}</button>
{{if someTask.isRunning 'Running'}}
{{my-component componentName="One"}}
<br>
{{my-component componentName="Two"}}

What’s Going On?

In the application.hbs template, we’ve rendered out the component twice. Since ember-concurrency tasks are scoped to instances (in this case, instances of a component), each rendered component gets its own, unique instance of myTask .

Each instance of myTask  keeps its own completely independent state, and that enables us to perform myTask  on both components simultaneously. To demonstrate: if we were to double-click Button One we see the text Running pop up next to Button One (but not Button Two) and we only see a single pair of logs in the console:

  • Start One
  • Done One

That’s ember-concurrency doing its job for us by preventing the task from being run a second time while the first is still running.

However, the same rule does not apply when we click Button Two immediately after Button One. When doing that, we see Running show up next to both buttons, and the following in the console:

  • Start One
  • Start Two
  • Done One
  • Done Two

Enter the Singleton

If necessary, we can get around this duplication effect by defining our tasks in singletons. In Ember, routes, controllers and services are all singletons, meaning only a single instance of any given route, controller, or service is created. Once created, they are never destroyed.

The obvious connection to ember-concurrency tasks is: if I had defined myTask on a route instead of in a component, it would not have been duplicated. In that case, we truly would only ever have one instance of myTask  running at a time, regardless of how many times or combinations of buttons we pressed.

And there you have it! This behaviour by ember-concurrency is definitely a sane default and likely the most commonly used behaviour. On the rare occasion you do need multiple sibling components to share task state, it could cause some unexpected bugs in your application if you’re not paying attention.

 

Banner image: Photo by Aleksandr Popov on Unsplash 

Media Suite
is now
MadeCurious.

All things change, and we change with them. But we're still here to help you build the right thing.

If you came looking for Media Suite, you've found us, we are now MadeCurious.

Media Suite MadeCurious.