Add throttling to AJAX calls

The endpoint that count characters should be pretty low-load because it
won’t talk to the database (unless, on the first request, the user and
service aren’t cached in Redis).

The response size is also very small, only one line of text wrapped in a
single `<span>`, so won’t be as CPU-intensive to render as a whole page.

Still, we don’t want to completely hammer the server if a user types
very quickly.

This commit adds some throttling, so that we wait until there’s a
certain amount of delay between keystrokes before firing off the request
to the backend.

I’ve set the delay at 150ms. At normal typing speed this makes the lag
feel fairly imperceptible – it feels like you get an updated count in
response to most keystrokes. It’s only if you really mash the keyboard
that the count won’t update until you take a breath.
This commit is contained in:
Chris Hill-Scott
2021-01-07 11:18:19 +00:00
parent c3b6c03411
commit d452c0081d
2 changed files with 60 additions and 2 deletions

View File

@@ -3,10 +3,40 @@
window.GOVUK.Modules.UpdateStatus = function() {
let getRenderer = $component => response => $component.html(
const getRenderer = $component => response => $component.html(
response.html
);
const throttle = (func, limit) => {
let throttleOn = false;
let callsHaveBeenThrottled = false;
let timeout;
return function() {
const args = arguments;
const context = this;
if (throttleOn) {
callsHaveBeenThrottled = true;
} else {
func.apply(context, args);
throttleOn = true;
}
clearTimeout(timeout);
timeout = setTimeout(() => {
throttleOn = false;
if (callsHaveBeenThrottled) func.apply(context, args);
callsHaveBeenThrottled = false;
}, limit);
};
};
this.start = component => {
let id = 'update-status';
@@ -19,7 +49,7 @@
this.$textbox
.attr('aria-described-by', this.$textbox.attr('aria-described-by') + ' ' + id)
.on('input', this.update)
.on('input', throttle(this.update, 150))
.trigger('input');
};

View File

@@ -127,10 +127,38 @@ describe('Update content', () => {
window.GOVUK.modules.start();
expect($.ajax.mock.calls.length).toEqual(1);
// 150ms of inactivity
jest.advanceTimersByTime(150);
helpers.triggerEvent(textarea, 'input');
expect($.ajax.mock.calls.length).toEqual(2);
});
test("It should fire only after 150ms of inactivity", () => {
let textarea = document.getElementById('template_content');
// Initial update triggered
window.GOVUK.modules.start();
expect($.ajax.mock.calls.length).toEqual(1);
helpers.triggerEvent(textarea, 'input');
jest.advanceTimersByTime(149);
expect($.ajax.mock.calls.length).toEqual(1);
helpers.triggerEvent(textarea, 'input');
jest.advanceTimersByTime(149);
expect($.ajax.mock.calls.length).toEqual(1);
helpers.triggerEvent(textarea, 'input');
jest.advanceTimersByTime(149);
expect($.ajax.mock.calls.length).toEqual(1);
// > 150ms of inactivity
jest.advanceTimersByTime(1);
expect($.ajax.mock.calls.length).toEqual(2);
});
});