So have you tried ember-concurrency yet?

Hey everyone,

If you haven’t seen it yet, please take a look at ember-concurrency. As the tagline says:

ember-concurrency is an Ember Addon that enables you to write concise, worry-free, cancelable, restartable, asynchronous tasks.

From the tagline alone, it might not sound all that compelling, but trust me, it really strikes at the root of so many problems (some you didn’t even know you had) when it comes to managing asynchronous/concurrent code in Ember with only promises/callbacks at your disposal.

At this point, there’s quite a bit of documentation that’s been produced, as well as a collection of examples of day-to-day use cases, so be sure to give those a look. If anything is unclear, please let me know here or open an issue on the GitHub repo. In the meantime, here is a (brief) list of problems / use cases that ember-concurrency solves:

  • Never write an if(this.isDestroyed) check ever again → instead, use a Task, which gets automatically canceled when the object it lives on is destroyed
  • Never explicitly cancel a timer ever again → instead, use yield timeout(ms) in a Task, and let the timer be canceled automatically when the task is canceled
  • Never worry if some asynchronous operation is going to cause problems if/when it continues running after the component that initiated it is unrendered → if you’ve implemented the operation as a Task, it’ll be canceled when the component is unrendered
  • Never have to set/unset your own isLoading/isProcessing/isRunning flags that wrap an async action/operation ever again so that buttons in your template can show up as disabled/unclickable → just use the isIdle/isRunning flags on the task object
  • Never write an if() statement in an action that returns early if the async process it’s supposed to start is already running → use a Task Modifier to annotate how to prevent unwanted concurrency
  • Never write your own half-baked implementation of cancelable promises → chances are what you’re trying to do can be expressed via a Task, which is cancelable, while still adhering to the Promise .then()-able interface
  • Never build your own bespoke artisanal async queue of operations using an un-cancelable chain of promises → use the .enqueue() Task Modifier

…and a ton a more.

Please try it out in your apps. There may be some churn in the API in the coming weeks, but at this point, I don’t foresee any major breaking changes, and based on the feedback I’ve received so far from some early adopters, you won’t regret giving it a try.

And whether you love it or hate it, please let me know how your experience with ember-concurrency is and how I can make it even better (or the documentation more clear).

Thanks, Alex

18 Likes

Funny, I was about to send you a shout out for this addon because is AWESOME!

I started using it 2 days ago and I’m simply amazed how easy was to implement a feature to poll stats from the backend and cancel on demand. Literally 5 lines of code and I had all the functionality I needed. My app is still close but I’ll open it on GH in the next few days.

The documentation was very clear to me and the examples helped a lot to understand what I needed to do.

Sorry I don’t have any real feedback as unfortunately, my current use case is very simple. Will definitely post back if I got more meaningful feedback, but just wanted to say thanks

2 Likes

That is a :100: documentation site, @machty. I’ve heard talk of ember-concurrency but this really shows me several places I could use it that I hadn’t realised.

1 Like

I just watched the video and read the docs and API. Really great documentation.

I’m excited to try this in my application. I have dozens of different commands which modify models, all managed by a do/undo/redo service. I haven’t quite wrapped my head around how, but I think I can use ember-concurrency to ensure that the ember-data promises resulting from these commands all resolve before starting the next command.

1 Like

Thanks for the nice feedback :slightly_smiling:

For anyone who hasn’t seen it yet, I think this video is the best intro to ember-concurrency and how it can solve many problems in your Ember apps (it’s longish, ~45m, but it’s a good one):

I’m here to help anyone get started with it. Also, I’d definitely like so feedback as to what the hurdles are for getting started.

My one day of experience with ember-concurrency so far has been exceptional. It’s brilliant. Thank you so much.

@1:57 in whipping-up saveTask, you say that you have to save the yielded ember-data save() promise in a temporary variable before returning it. If that’s true, why?

I am using the same sort of save task in a service, but it accepts a model-to-be-saved argument. This centralizes error handling and lets me limit concurrency. Really nice.

One point you might make earlier or emphasize in the docs is that you yield a promise – any promise such as model.save() – and that task instances returned by perform() are promises. Since the examples all use timeout(), they are a little harder to translate to how one would apply ember-concurrency to real work. This helped turn on the light bulb for me.

Similarly, the point that yield returns the resolved data form the promise and that the code following won’t resume until resolution is important to grokking ember-concurrency.

@DanChadwick it’s actually fine to just return the promise without yielding it first in that case; there was a small bug in e-c that I fixed since the presentation that does affect the xhr-canceling autocomplete example: http://alexmatchneer.com/ec-prezo/#/?slide=autocomplete-example-2

Specifically, the getJSON task has to yield the promise into a value first before returning it because if it just returned the $.getJSON promise, then it would have immediately executed that finally block and canceled the xhr right away. But if the promise you’re “taskifying” doesn’t need to be wrapped in try-finally, then you can just return it from the task and it’ll resolve the promise before considering itself finished (and yielding control back to the parent task).

That’s a good point about timeout() and how yielding promises works, and I’ve heard similar feedback from others. I think I introduce it better in the slides and need to incorporate that approach back into the docs.

@DanChadwick actually, fun fact that I just learned myself, you don’t have to store the yield in another variable, you can just return yield somePromise; to wait for the promise to fulfill before executing finally blocks, but I’m going to leave the autocomplete example in its current state for clarity’s sake.

Somehow this feels like what actions should be. Can you straight up define an action handler to just be a task?

I learned something new today and I can’t believe how beautifully simple ember-concurrent is. The video presentation went a long to explain the why and what. Still in the process of getting my ember footing, yet I learned of other problems I was not yet aware of. Thanks for sharing!

@machty: Another tip for the docs might be to add " noyield": true, to the jshint configuration file so that tasks (i.e. generators) without a yield in them aren’t flagged. I found that I had a number of these situations.

I also tried not using the babel polyfill, since firefox had generators, but it didn’t work. I thought that might make debugging easier. Oh well. Sprinkling debugger; and breakpoints in the tasks is much easier that trying to step through them. So much runtime to wade through…

@lightblade there are somewhat complicated reasons tasks can’t live on the actions hash, but one reason it’s nice to have it as a property on the object is that you can directly bind to myTask.isRunning and other properties directly within the template.

@DanChadwick I’m not certain Firefox generators are up to spec (they definitely weren’t for a long time). Even generators in node aren’t up to spec; I’m afraid regenerator is somewhat required at this point. But that’s a good idea about noyield; I’ll open an issue about that.

@lightblade Another plus: Because tasks are properties, you can alias them:

redoTask: Ember.computed.alias('doTask'),
1 Like

I’m kinda confused about how to use this right. Right now i’m having a save action in my route and passing it down to a component. Where should I have my task?

Route:

save(post) {
    post.save().then(() => {
        // transition & notice
    }
}

Template: {{post-form post=post on-submit=(route-action "save" post)}}

Component: <button {{action on-submit}}>Save</button>

Read up on this yesterday and already used it twice today with great results and a big reduction in code.

This is such a clever implementation that I’d be surprised if the Ember core team didn’t try to incorporate it at some stage. In the meantime, I’d encourage everyone to take a look, I’d be amazed if it didn’t have relevance to all but the simplest apps.

Thanks Alex!