Throttling Ember-Data with Ember-Concurrency

Derek Gray
2 min readMar 17, 2019

My project’s demo was nearly crippled recently when the number of JSON-API CRUD operations to the server was outstripping the database connection pool and creating total havoc on the consistency of the data on the client. Here’s a quick fix trick to throttling the AJAX requests that Ember-Data makes.

We were using stock Ember-Data Store CRUD and had not addressed the lack of “bulk” operations. In the application we were doing lots of duplication and/or deletion of many records at once (or rather, in very quick succession.) Once we’d determined that throttling the AJAX requests would patch the problem the question became how to cover the many calls throughout the application.

Ember-Concurrency to the rescue

Wouldn’t you know it, the Ember-Concurrency documentation has a beautiful example of throttling AJAX calls as a use-case for the maxConcurrency() task annotation:

ajaxTask: task(function * () {
// simulate slow AJAX
yield timeout(2000 + 2000 * Math.random());
return {};
}).enqueue().maxConcurrency(3)

Great — so how do we insert ourselves into the AJAX calls that ember-data is performing on our behalf? We can extend the application Adapter. We already had a customized adapter to fix our URL text casing to match our server’s expectations. We also use the ember-ajax addon along with their mixin for ember-data, so all calls are made through the ajax service:

// app/adapters/application.js
import DS from 'ember-data';
import AjaxServiceSupport from 'ember-ajax/mixins/ajax-support';

export default DS.JSONAPIAdapter.extend(AjaxServiceSupport);

Mixing in the Throttle

Armed with the ajax service, we can add another mixin of our own to override the calls and throttle them:

// app/mixins/throttled-ajax.jsconst MAX_CONCURRENT_JSONAPI = 15; // DB pool is higher than this

export default Mixin.create({

_ajaxTask: task(function * (url, augmentedOptions) {
return yield get(this, 'ajaxService')
.request(url, augmentedOptions);
}).enqueue().maxConcurrency(MAX_CONCURRENT_JSONAPI),

/**
*
@override
*/
ajax(url, _method/*, _options*/) {
const augmentedOptions = this.ajaxOptions(...arguments);
return this._ajaxTask.perform(url, augmentedOptions);
}
});

Then just add the new mixin to the adapter to further override the ember-ajax mixin:

// app/adapters/application.js
import DS from 'ember-data';
import AjaxServiceSupport from 'ember-ajax/mixins/ajax-support';
import ThrottledAjax from 'myapp/mixins/throttled-ajax';
export default DS.JSONAPIAdapter.extend(AjaxServiceSupport,
ThrottledAjax);

There are other solutions of course, but those required more thought and refactoring that just wasn’t possible while under the gun for a hot-fix. This was quick and easy.

Photo: https://www.flickr.com/photos/internetarchivebookimages/tags/bookidrailwaylocomotiv19newy

--

--