QueueCumber is a little library that tries to prevent data loss due to connection problems. Whenever the server returns an error, QueueCumber will hold on to the request and retry it until it succeeds. QueueCumber is configurable per request however, so this behavior can be changed. It's designed to work with Backbone, and ties in to its sync method.
In modern browsers, stored requests will survive page refreshes because they are stored in localStorage.
QueueCumber needs to be instantiated in order to work. For our example, we'll create a global instance:
var queueCumber = new QueueCumber();Next, QueueCumber should be intergrated into Backbone. To do that, you'll have to override the sync method — this isn't done automatically. A very simple example would be:
sync: function( method, model, options ) {
options || (options = {});
var params = {
'type': QueueCumber.methodMap[method],
'dataType': 'json'
};
if( !options.url ) {
params['url'] = _.result(model, 'url');
}
// Ensure that we have the appropriate request data.
if(
model && options.data == null &&
(method === 'create' || method === 'update' || method === 'patch')
) {
params['data'] = JSON.stringify(options.attrs || model.toJSON(options));
}
// `queueCumber` is a QueueCumber instance.
queueCumber.add(model, params, options);
}With a sync method in place, we're done. You can now use Backbone like you normally would. All requests are routed throught QueueCumber, and if a non-GET request fails, it will retry until it succeeds.
The API method to add a request to the queue is queueCumber.add(model, params, options):
modelis a Backbone model.paramsis normally created by Backbone'ssyncmethod. It can contain everything a normal jQueryajaxcall accepts. Keep in mind that this object will be serialized, so adding callback functions won't work. QueueCumber will assure that some properties are always present:
'type': 'GET',
'contentType': 'application/json',
'processData': false,
'url': 'http://url',
'data': '{"json": true}',
'headers': {
'X-First-Requested': parseInt(+new Date/1000),
'X-Request-GUID': guidString
}X-First-Requested is set when the request is first made. This way the server you can implement backend logic that can put incoming requests in the proper order, since it's possible they won't arrive in chronological order. See the chapter Backend requirements.
X-Request-GUID is a GUID which is used in QueueCumber to uniquely identify a request. It's also sent to the server, so you can implement something in your backend that prevents replaying of requests.
optionsis your usual Backbone options object. This object is not stored in localStorage, since it can contain non-scalar data (objects, functions). However, a few options will be picked up by QueueCumber, and permanently stored in the request:
'url': url,
'data': '{"json":true}',
'maxRetries': 3Every time a request is added to QueueCumber, it will create a request object. This is a plain JavaScript object:
{
'guid': guid,
'params': params,
'options': options,
'model': model,
// Current request state.
'status': 'IDLE',
'last': +new Date,
'next': 0,
'tries': 0,
'maxRetries': options.maxRetries || params['maxRetries'],
// In case of an error, this will store the last response.
// Like: {'error': 'message'}.
'lastResponse': null,
// If this request exceeded its maxRetries, it will be removed from
// the queue, and this property will be set to true. Although removed
// from the queue, this property can be checked for in the `error` event.
'removed': false,
'title': options.title,
'description': options.description
}QueueCumber uses Backbone's event system, so you can keep tabs on what's happening from the outside. To listen to events, you can use the familiar Backbone API methods.
This is a list of all events. Every request has two arguments: request, a request object as described above, and queue, which is a QueueCumber instance.
- "add" (request, queue) — A request is added to the queue. Non-GET requests only.
- "busy" (request, queue) — A request is picked up for processing. Doesn't fire for GET.
- "success" (request, queue) — A request was completed successfully. Doesn't fire for GET.
- "error" (request, queue) — The server returned an error. DOES fire for GET.
- "removed" (request, queue) — When a request is removed from the queue. This can happen manually, or because the amount of retries has reached
maxRetries.
To get started with QueueCumber is very easy, but to get it working correctly all the time will require some backend work. QueueCumber can't provide in this because every situation requires its own specific solution.
The most important issue is asynchronicity: in case of bad connections or server problems, requests don't necessarily arrive in synchronous order. In some cases the backend will need to account for this, or data will be stored the wrong way. For this, QueueCumber sends a X-First-Requested header along with every request it makes. This header holds a UNIX timestamp indicating when the request was first tried.
The other issue is double requests. In some very specific situations where multiple tabs are opened it's possible that one request gets sent out multiple times. Although it's very unusual, and it will probably be fixed later on, it is possible and should be accounted for. QueueCumber send along a X-Request-GUID header containing a unique GUID, so every request can be uniquely identified on the server.