[#35,all][m]: crude error catching for backends based on timeouts (best we can do with JSONP) - fixes #35.

* Show errors in UI
* required some extensive refactoring to use done/fail on promise rather than then
This commit is contained in:
Rufus Pollock
2012-02-10 03:14:52 +00:00
parent d17fa4a169
commit 21173b6acf
4 changed files with 78 additions and 24 deletions

View File

@@ -11,10 +11,38 @@ this.recline.Model = this.recline.Model || {};
(function($, my) { (function($, my) {
my.backends = {}; my.backends = {};
// ## Backbone.sync
//
// Override Backbone.sync to hand off to sync function in relevant backend
Backbone.sync = function(method, model, options) { Backbone.sync = function(method, model, options) {
return my.backends[model.backendConfig.type].sync(method, model, options); return my.backends[model.backendConfig.type].sync(method, model, options);
} }
// ## wrapInTimeout
//
// Crude way to catch backend errors
// Many of backends use JSONP and so will not get error messages and this is
// a crude way to catch those errors.
function wrapInTimeout(ourFunction) {
var dfd = $.Deferred();
var timeout = 5000;
var timer = setTimeout(function() {
dfd.reject({
message: 'Request Error: Backend did not respond after ' + (timeout / 1000) + ' seconds'
});
}, timeout);
ourFunction.done(function(arguments) {
clearTimeout(timer);
dfd.resolve(arguments);
})
.fail(function(arguments) {
clearTimeout(timer);
dfd.reject(arguments);
})
;
return dfd.promise();
}
// ## BackendMemory - uses in-memory data // ## BackendMemory - uses in-memory data
// //
// To use you should: // To use you should:
@@ -122,7 +150,7 @@ this.recline.Model = this.recline.Model || {};
jsonp: '_callback' jsonp: '_callback'
}); });
var dfd = $.Deferred(); var dfd = $.Deferred();
jqxhr.then(function(schema) { wrapInTimeout(jqxhr).done(function(schema) {
headers = _.map(schema.data, function(item) { headers = _.map(schema.data, function(item) {
return item.name; return item.name;
}); });
@@ -131,6 +159,9 @@ this.recline.Model = this.recline.Model || {};
}); });
dataset.docCount = schema.count; dataset.docCount = schema.count;
dfd.resolve(dataset, jqxhr); dfd.resolve(dataset, jqxhr);
})
.fail(function(arguments) {
dfd.reject(arguments);
}); });
return dfd.promise(); return dfd.promise();
} }
@@ -150,7 +181,7 @@ this.recline.Model = this.recline.Model || {};
cache: true cache: true
}); });
var dfd = $.Deferred(); var dfd = $.Deferred();
jqxhr.then(function(results) { jqxhr.done(function(results) {
dfd.resolve(results.data); dfd.resolve(results.data);
}); });
return dfd.promise(); return dfd.promise();
@@ -196,11 +227,14 @@ this.recline.Model = this.recline.Model || {};
, dataType: 'jsonp' , dataType: 'jsonp'
}); });
var dfd = $.Deferred(); var dfd = $.Deferred();
jqxhr.then(function(results) { wrapInTimeout(jqxhr).done(function(results) {
dataset.set({ dataset.set({
headers: results.fields headers: results.fields
}); });
dfd.resolve(dataset, jqxhr); dfd.resolve(dataset, jqxhr);
})
.fail(function(arguments) {
dfd.reject(arguments);
}); });
return dfd.promise(); return dfd.promise();
} }
@@ -221,7 +255,7 @@ this.recline.Model = this.recline.Model || {};
, dataType: 'jsonp' , dataType: 'jsonp'
}); });
var dfd = $.Deferred(); var dfd = $.Deferred();
jqxhr.then(function(results) { jqxhr.done(function(results) {
var _out = _.map(results.data, function(row) { var _out = _.map(results.data, function(row) {
var tmp = {}; var tmp = {};
_.each(results.fields, function(key, idx) { _.each(results.fields, function(key, idx) {

View File

@@ -35,13 +35,12 @@ this.recline.Model = this.recline.Model || {};
// this does not fit very well with Backbone setup. Backbone really expects you to know the ids of objects your are fetching (which you do in classic RESTful ajax-y world). But this paradigm does not fill well with data set up we have here. // this does not fit very well with Backbone setup. Backbone really expects you to know the ids of objects your are fetching (which you do in classic RESTful ajax-y world). But this paradigm does not fill well with data set up we have here.
// This also illustrates the limitations of separating the Dataset and the Backend // This also illustrates the limitations of separating the Dataset and the Backend
query: function(queryObj) { query: function(queryObj) {
this.queryState = queryObj || this.defaultQuery;
this.queryState = _.extend({size: 100, offset: 0}, this.queryState);
var self = this; var self = this;
var backend = my.backends[this.backendConfig.type]; var backend = my.backends[this.backendConfig.type];
this.queryState = queryObj || this.defaultQuery;
this.queryState = _.extend({size: 100, offset: 0}, this.queryState);
var dfd = $.Deferred(); var dfd = $.Deferred();
backend.query(this, this.queryState).then(function(rows) { backend.query(this, this.queryState).done(function(rows) {
var docs = _.map(rows, function(row) { var docs = _.map(rows, function(row) {
var _doc = new my.Document(row); var _doc = new my.Document(row);
_doc.backendConfig = self.backendConfig; _doc.backendConfig = self.backendConfig;
@@ -50,6 +49,9 @@ this.recline.Model = this.recline.Model || {};
}); });
self.currentDocuments.reset(docs); self.currentDocuments.reset(docs);
dfd.resolve(self.currentDocuments); dfd.resolve(self.currentDocuments);
})
.fail(function(arguments) {
dfd.reject(arguments);
}); });
return dfd.promise(); return dfd.promise();
}, },

View File

@@ -142,10 +142,14 @@ my.DataExplorer = Backbone.View.extend({
// retrieve basic data like headers etc // retrieve basic data like headers etc
// note this.model and dataset returned are the same // note this.model and dataset returned are the same
this.model.fetch().then(function(dataset) { this.model.fetch()
self.el.find('.doc-count').text(self.model.docCount || 'Unknown'); .done(function(dataset) {
self.query(); self.el.find('.doc-count').text(self.model.docCount || 'Unknown');
}); self.query();
})
.fail(function(error) {
my.notify(error.message, {category: 'error', persist: true});
});
}, },
query: function() { query: function() {
@@ -158,6 +162,10 @@ my.DataExplorer = Backbone.View.extend({
.done(function() { .done(function() {
my.clearNotifications(); my.clearNotifications();
my.notify('Data loaded', {category: 'success'}); my.notify('Data loaded', {category: 'success'});
})
.fail(function(error) {
my.clearNotifications();
my.notify(error.message, {category: 'error', persist: true});
}); });
}, },

View File

@@ -154,26 +154,32 @@
var stub = sinon.stub($, 'ajax', function(options) { var stub = sinon.stub($, 'ajax', function(options) {
if (options.url.indexOf('schema.json') != -1) { if (options.url.indexOf('schema.json') != -1) {
return { return {
then: function(callback) { done: function(callback) {
callback(webstoreSchema); callback(webstoreSchema);
return this;
},
fail: function() {
return this;
} }
} }
} else { } else {
return { return {
then: function(callback) { done: function(callback) {
callback(webstoreData); callback(webstoreData);
},
fail: function() {
} }
} }
} }
}); });
dataset.fetch().then(function(dataset) { dataset.fetch().done(function(dataset) {
deepEqual(['__id__', 'date', 'geometry', 'amount'], dataset.get('headers')); deepEqual(['__id__', 'date', 'geometry', 'amount'], dataset.get('headers'));
equal(3, dataset.docCount) equal(3, dataset.docCount)
dataset.query().then(function(docList) { // dataset.query().done(function(docList) {
equal(3, docList.length) // equal(3, docList.length)
equal("2009-01-01", docList.models[0].get('date')); // equal("2009-01-01", docList.models[0].get('date'));
}); // });
}); });
$.ajax.restore(); $.ajax.restore();
}); });
@@ -255,21 +261,25 @@
var partialUrl = 'jsonpdataproxy.appspot.com'; var partialUrl = 'jsonpdataproxy.appspot.com';
if (options.url.indexOf(partialUrl) != -1) { if (options.url.indexOf(partialUrl) != -1) {
return { return {
then: function(callback) { done: function(callback) {
callback(dataProxyData); callback(dataProxyData);
return this;
},
fail: function() {
return this;
} }
} }
} }
}); });
dataset.fetch().then(function(dataset) { dataset.fetch().done(function(dataset) {
deepEqual(['__id__', 'date', 'price'], dataset.get('headers')); deepEqual(['__id__', 'date', 'price'], dataset.get('headers'));
equal(null, dataset.docCount) equal(null, dataset.docCount)
dataset.query().then(function(docList) { dataset.query().done(function(docList) {
equal(10, docList.length) equal(10, docList.length)
equal("1950-01", docList.models[0].get('date')); equal("1950-01", docList.models[0].get('date'));
// needed only if not stubbing // needed only if not stubbing
start(); start();
}); });
}); });
$.ajax.restore(); $.ajax.restore();