Fig. 5: By storing previously successful tasks, we can show data updates. Here, we leave a route and re-enter it to trigger a data update.

Benefits for both parties in a web app

A large web app is successful if users enjoy navigating it and if developers enjoy building it. While ember-concurrency was written with the intention of reducing boilerplate code and enforcing logical boundaries, our full-scale adoption of the add-on has made the user experience much smoother. We have built an app that demonstrates the benefits of single-page applications: route transitions feel extremely speedy because the model hook returns simple objects without pausing to resolve promises, and the user receives instant visual feedback about network operations in response to clicks. Pages that retrieve data from multiple sources can display it in a progressive way and minimize idle time where the user cannot interact with the page. Responsive filter-based interfaces reflect data updates in a clear and dynamic way.

Many JavaScript developers, myself included, can relate to the frustration of staring at a promise chain or callback functions to understand a particular execution flow. JavaScript’s powerful concurrency model and event loop become very cumbersome to manage in complex apps, where multiple components and routes multiply the unpredictability of user interaction and network latency.

ember-concurrency's mission to improve developer experience has been a great success. When futuristic ES6 features like generator functions were introduced, many developers nodded their head and shrugged; it seemed like a different way to execute the same asynchronous operations, with no obvious breakthrough implementation. A few years later, ember-concurrency has established itself as one of the most dynamic applications of generator functions. We now write readable code without convoluted promise callbacks. The browser uses an event loop to handle asynchrony, but our brains have an easier time understanding logic that looks synchronous. Like other modern JavaScript features such as async/await, generator functions abstract the complexity of the run loop into structured, linear code that mimics a flat timeline. Developers like us are the big winners, as our code is more readable and maintainable.

A glimpse into the future of JavaScript

Tasks reflect a shift from the philosophy of promise-based frameworks: whereas promises ensure that specific code will be run, the unpredictability of the web is such that making future guarantees behind time-based operations is dangerous. Tasks that are bound to components offer a much more reassuring guarantee, assuring us that code will be executed as long as the component is active and the task is not explicitly canceled. We are much more comfortable and engaged writing code because we know that an add-on is abstracting out the boilerplate logic.

ember-concurrency is still not perfect, and our work in internal tools has shielded us from the few downsides of using it. By assuming that our users use modern browsers on a fast connection, we could afford to take a flyer on a library that initially required heavy Babel polyfills (though this is no longer the case) and did not provide easy server-side rendering support with Ember FastBoot. Several months later, the add-on feels more mature, and we have been so happy with it that we have entirely eradicated promises from our code base. By going at the heart of issues like developer convenience and concurrency management, it is one of the best concrete applications of futuristic JavaScript features. Give it a shot, we guarantee it’ll change the way you write Ember.

Acknowledgements

Warm thanks to Alex Matchneer for building this truly game-changing add-on and helping me understand the motivations behind it, to Josh Lawrence for his willingness to take calculated risks in our big project, and to the rest of the Centralized Release Tool team for their tremendous work on highlighting the power of ember-concurrency.