2017-03-01 10:20:04 | development

The Appearance of Efficiency

AJAX's problem is that the user is left waiting for the browser to interact with the server, because near-enough every AJAX tutorial out there follows this pattern:

$(document).ready(function() {

    $('#form').submit(function(e) {
        e.preventDefault();

        var $form = $(this);
        var $actionButton = $form.find('button[type="submit"]');

        // Disable the button to prevent multiple submits.
        $actionButton.prop('disabled', true);

        // Show a loading icon to give user some form of clue.
        var buttonText = $actionButton.html();
        $actionButton.html('<img src="loading.gif">');

        // Remove any existing error containers.
        $('.error').remove();

        $.post('/path/to/server/action', $form.serialize(), function(response) {

            if (response.success) {
                // Add a new row to the listing.
                $('#listing').append('<li>'+ response.hobbyName +'</li>');
            } else {
                // Something probably didn't validate..
                $form.before('<span class="error">'+ response.errorMsg +'</span>');
            }

            // Re-enable button.
            $actionButton.prop('disabled', false);
            $actionButton.html(buttonText);

        });
    });

});

 

The request itself typically takes less than a second which sounds tiny, but it is noticable. It feels very "web-like", even though we can take minimal extra effort to noticably improve the user's experience.

It is rare (or should be) for the server to spit out a 403 or 404 status (which we can handle with $(document).ajaxError()). Most actions will be successful, unless the user is trying to do something nafarious or there's no attempt being made to deny their ability to action the request based on their permissions. With this in mind, we can give the appearance of efficiency by performing the best-case result straight away, before the AJAX request is even made. The AJAX request still happens, but doesn't need to do much other than tell us something was wrong, in which case we can revert the intended success actions:

 

$(document).ready(function() {

    $('#form').submit(function(e) {
        e.preventDefault();

        var $form = $(this);

        // Remove any existing error containers.
        $('.error').remove();

        // Add a new row to the listing.
        var $newLi = $('<li>'+ $('input["name="hobbyName"]').val() +'</li>');
        $('#listing').append($newLi);
        $newLi.fadeIn(200);

        $.post('server/path', $form.serialize(), function(response) {

            if (!response.success) {
                $form.before('<span class="error">'+ response.errorMsg +'</span>');
                $newLi.remove();
            }

        });
    });

});

 

The outcome is the same in both samples, but we get there more smoothly with the optimistic version. We are giving the front-end more responsibility which is a good thing on today's web. It is essentially the living, breathing app, able to respond to the user's situation there and then as opposed to being a mere agent between the user and the back-end in the "traditional" web. The server is still policing the data, but the front-end does it's thing optimistically until the "server says no". As a bonus, the code difference - in this case - is less than the traditional method and arguably cleaner and more to the point. Look at the stories being told by each version:

 

Traditional

  1. Stop the form from submitting and handle it ourself.
  2. Prevent the user from re-submitting the form by disabling the button that triggered this.
  3. Show some indication to the user that we are actually doing something about their request.
  4. Just in case, remove any existing error containers.
  5. Send the request, and wait for the response.
  6. Was the response a success? Add the item to our list If not, show an error.
  7. In either case, re-enable the button, and reset it's text to how it was previously.

Optimistic

  1. Stop the form from submitting and handle it ourself.
  2. Just in case, remove any existing error containers.
  3. Add the new item to our listing. Preferably fade this in, or some other animation to make the result clear.
  4. Send the request to the server, so the new item can be validated and saved.
  5. Was the response problematic? Roll back our action by removing the item from the list, and display an error.

 

If any errors happen, the reversal is instant. This is by design, and should be consistent through the app. We animate any intended actions, and immediately action any reversals and show an error message indicating something was wrong. The user is drawn to the error message, rather than the item popping off the listing. They will notice the list was changed, the error will say why.

If the user wishes to actually delete an item, we animate the removal (such as a fade out) and pop it back in if something went wrong. The user still gets an error message to say why.

I have setup a demo to compare the approaches. As always, feel free to let me know your thoughts via Twitter.

signature