It's an interesting time to be working on the frontend now. We have new technologies such as HTML5, CSS3, Canvas and WebGL; all of which greatly increase the possibilities for web application development. The world is our oyster!
However, there's also another trend I've noticed. Web developers are still stuck in the request/response mindset. I call it the 'click and wait' approach - where every UI interaction results in a delay before another interaction can be performed. That's the process they've used their entire careers so it's no wonder most developers are blinkered to the alternatives.
Speed matters; a lot. Or to be precise, perceived speed matters a lot. Speed is a critical and often neglected part of UI design, but it can make a huge difference to user experience, engagement and revenue.
- Amazon: 100 ms of extra load time caused a 1% drop in sales (source: Greg Linden, Amazon).
- Google: 500 ms of extra load time caused 20% fewer searches (source: Marrissa Mayer, Google).
- Yahoo!: 400 ms of extra load time caused a 5–9% increase in the number of people who clicked “back” before the page even loaded (source: Nicole Sullivan, Yahoo!).
Yet, despite all this evidence, developers still insist on using the request/response model. Even the introduction of Ajax hasn't improved the scene much, replacing blank loading states with spinners. There's no technical reason why we're still in this state of affairs, it's purely conceptual.
A good example of the problem is Gmail's 'sending' notification; how is this useful to people? What's the point of blocking? 99% of the time the email will be sent just fine.
As developers, we should optimize for the most likely scenario. Behavior like this reminds me of Window's balloon notifications, which were awesome for telling you about something you just did:
The key thing to remember is that users don't care about Ajax. They don't give a damn if a request to the server is still pending. They don't want loading messages. Users would just like to use your application without any interruptions.
AUIs result in a significantly better user experience, more akin to what people are used to on the desktop than the web. Here's an example of an AUI Spine application with a Rails backend.
Notice that any action you take, such as updating a page, is completely asynchronous and instant. Ajax REST calls are sent off to Rails in the background after the UI has updated. It's a much better user experience.
Compare it to the static version of the same application, which blocks and navigates to a new page on every interaction. The AUI experience is a big improvement, that will get even more noticeable in larger (and slower) applications.
Not a silver bullet
It's worth mentioning here that I don't think this approach is a silver bullet for all web applications, and it won't be appropriate for all use cases. One example that springs to mind is credit-card transactions, something you'll always want to be synchronous and blocking. However, I do believe that AUIs are applicable the vast majority of the time.
The other point I'd like to make is that not all feedback is bad. Unobtrusive feedback that's actually useful to your users is completely fine; like a spinner indicating when files have synced, or network connections have finished. The key thing is that feedback is useful and doesn't block further interaction.
So how do you achieve these AUIs? There are a number of key principles:
- Move state & view rendering to the client side
- Intelligently preload data
- Asynchronous server communication
Now, these concepts turn the existing server-driven model on its head. It's often not possible to convert a conventional web application into a client side app; you need to set out from the get go with these concepts in mind as they involve a significantly different architecture.
The idea is that you update the client before you send an Ajax request to the server. For example, say a user updated a page name in a CMS. With an asynchronous UI, the name change would be immediately reflected in the application, without any loading or pending messages. The UI is available for further interaction instantly. The Ajax request specifying the name change would then be sent off separately in the background. At no point does the application depend on the Ajax request for further interaction.
For example, let's take a Spine Model called
Page. Say we update it in a controller, changing its name:
page = Page.find(1) page.name = "Hello World" page.save()
As soon as you call
save(), Spine will perform the following actions:
- Run validation callbacks and persist the changes to memory
- Fire the change event and update the user interface
- Send an Ajax PUT to the server indicating the change
Notice that the Ajax request to the server has been sent after the UI has been updated, in other words what we've got here is an asynchronous interface.
Now this is all very well in principle, but I'm sure you're already thinking of scenarios where this breaks down. Since I've been working with these types of applications for a while, I can hopefully address some of these concerns. Feel free to skip the next few sections if you're not interested in the finer details.
What if server validation fails? The client thinks the action has already succeeded, so they'll be pretty surprised if subsequently told that the validation had failed.
There's a pretty simple solution to this: client-side validation. Replicate server-side validation on the client side, performing the same checks. You'll always ultimately need server-side validation, since you can't trust clients. But, by also validating on the client you can be confident a request will be successful. Server-side validation should only fail if there's a flaw in your client-side validation.
That said, not all validation is possible on the client-side, especially validating the uniqueness of an attribute (an operation which requires DB access). There's no easy solution to this, but there is a discussion covering various options in Spine's documentation.
Network failures & server errors
What happens if the user closes their browser before a request has completed? This is fairly simple to resolve, just listen to the
window.onbeforeunload event, check to see if Ajax requests are still pending, and if appropriate notify the user. Spine's Ajax documentation contains a discussion about this.
window.onbeforeunload = -> if Spine.Ajax.pending '''Data is still being sent to the server; you may lose unsaved changes if you close the page.'''
Alternatively, if the server returns an unsuccessful response code, say a
500, or the network request fails, we can catch that in a global error handler and notify the user. Again, this is an exceptional event, so it's not worth investing too much developer time into. We can just log the event, inform the user and perhaps refresh the page to re-sync state.
Generating IDs on the server has the advantage that IDs are guaranteed to be unique, but generating them on the client side has the advantage that they're available instantly. How do we resolve this situation?
Well, a solution that Backbone uses is generating an internal
cid (or client id). You can use this
cid temporarily before the server responds with the real identifier. Backbone has a separate record retrieval API, depending on whether you're using a
cid, or a real
I'm not such a fan of that solution, so I've taken a different tack with Spine. Spine generates pseudo GUIDs internally when creating records (unless you specify an ID yourself). It'll use that ID to identify records from then on. However, if the response from an Ajax create request to the server returns a new ID, Spine will switch to using the server specified ID. Both the new and old ID will still work, and the API to find records is still the same.
The last issue is with Ajax requests that get sent out in parallel. If a user creates a record, and then immediately updates the same record, two Ajax requests will be sent out at the same time, a
POST and a
PUT. However, if the server processes the 'update' request before the 'create' one, it'll freak out. It has no idea what record needs updating, as the record hasn't been created yet.
The solution to this is to pipeline Ajax requests, transmitting them serially. Spine does this by default, queuing up
DELETE Ajax requests so they're sent one at a time. The next request sent only after the previous one has returned successfully.
So that's a pretty thorough introduction into asynchronous interfaces, and even if you glossed over the finer details, I hope you've been left with the impression that AUIs are a huge improvement over the status quo, and a valid option when building web applications.
This is a repost of an article first posted in November 2011