I have some really weird stuff going on here. I have a model that I'm attempting a fetch on. I can look at the calls in Fiddler or call my API service manually and I get the JSON I would expect back, but the fetch method always throws an error in Backbone. It does not tell me why and the responseText is empty. In fiddler, the service returns a 200.
Here's the calling code...
this.createSession = function (sessionId) {
var that = this;
CookieManager.CreateSession(sessionId);
var sessionData = new Authentication.Response({
id: sessionId
});
sessionData.fetch({
success: function () {
that.storeData(sessionData);
},
error: function (model, xhr) {
if (console !== undefined && console !== null) {
console.log('Unable to load session data: ' + xhr.responseText);
}
throw new Error('Unable to load session data');
}
});
};
And the model concerned...
define(['ministry', 'moment'],
function (Ministry) {
var authRequest = Ministry.Model.extend({
name: 'Auth Request',
urlRoot: '/api/auth',
defaults: {
id: undefined,
requestSource: undefined,
apiKey: undefined,
username: undefined,
password: undefined,
dateCreated: undefined,
lastLoggedIn: undefined
},
generatedHash: undefined,
parse: function(attributes, response) {
var hashUrl = response.xhr.getResponseHeader('location');
var elements = hashUrl.split("/");
this.generatedHash = attributes.id = elements[elements.length - 1].toLowerCase();
return attributes;
},
validate: function (attributes) {
if (attributes.requestSource === undefined || attributes.requestSource == null || attributes.requestSource == '') {
return "The attribute 'requestSource' is required";
}
if (attributes.apiKey === undefined || attributes.apiKey == null || attributes.apiKey == '') {
return "The attribute 'apiKey' is required";
}
if (attributes.username === undefined || attributes.username == null || attributes.username == '') {
return "The attribute 'username' is required";
}
if (attributes.password === undefined || attributes.password == null || attributes.password == '') {
return "The attribute 'password' is required";
}
return null;
}
});
// Return as two types for syntactic sugar reasons.
return {
Request: authRequest,
Response: authRequest
};
});
The model extends my common root model, the code for which is here...
var ministryModel = Backbone.Model.extend({
name: 'Unnamed Model',
initialize: function (options) {
this.options = options || {};
Backbone.Model.prototype.initialize.call(this, options);
if (this.options.name) {
this.name = this.options.name;
}
this.on('invalid', function (model, error) {
if (console !== undefined) {
console.log(error);
console.log(model);
}
throw (new Error(error));
});
},
fetch: function (options) {
this.trigger('fetching', this, options);
return Backbone.Model.prototype.fetch.call(this, options);
}
});
The error callback is always being called and I can't figure out why. In Fiddler I get the following from calling the API directly...
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 19 Aug 2013 16:12:00 GMT
Content-Length: 213
{"id":"bba5e742d1af3de5921e8df0a1818530789c202a","dateCreated":"2013-07-22T17:57:20.143","lastLoggedIn":"2013-08-19T17:08:29.67","username":"tiefling","apiKey":"ka73nd9s7n2ls9f3ka2n5p2k","requestSource":"website"}
And this when interrogating the call from Chrome...
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 19 Aug 2013 16:08:29 GMT
Content-Length: 213
{"id":"bba5e742d1af3de5921e8df0a1818530789c202a","dateCreated":"2013-07-22T17:57:20.143","lastLoggedIn":"2013-08-19T17:08:29.67","username":"tiefling","apiKey":"ka73nd9s7n2ls9f3ka2n5p2k","requestSource":"website"}
Both show 200 but show a slightly different Icon in Fiddler - The Chrome call icon is a red circle, but other than that everything looks hunky dory.
Any ideas welcome, I've hit a bit of a brick wall with this one for now.
ADDITIONAL: If I step through the code, when I get to the fetch call, the attributes within the 'sessionData' object haven't been updated at all. The object is exactly the same as the before the fetch method was executed.
UPDATE: Since I first wrote this, the code has now stopped making the call at all as far as I can see from Fiddler. This was working earlier, but now seems to be failing invisibly with no indication of what the error could be. Debugging the API code indicates that no call is now being made at all to the GET method on the service.
FURTHER UPDATE: Without changing any code except for removing the error / success callbacks, this started working (kind of). If I put a breakpoint in after the fetch call then it works completely normally (from what I can see in Fiddler - I can't check the app as the success callback is unwired). If I take out the breakpoint it now partially fails (red circle in Fiddler but a 200 response).
This seems to be some kind of race condition?
I've tried getting info using a 'complete' callback to use textStatus, but that just says 'error' - not particularly useful!
I finally found the answer to this; which isn't obvious in the above code. The calling code is being called from a higher method like this...
var authData = SiansPlanApp.session.getData(this.$loginUsername.val(), this.$loginPassword.val());
authData.save(authData.attributes, {
success: function() {
if (authData.generatedHash !== undefined && authData.generatedHash !== null && authData.generatedHash !== '')
MyApp.session.createSession(authData.generatedHash);
that.redirectToOrigin();
$.fancybox.close();
},
error: function (model, xhr) {
if (console !== undefined && console !== null) {
console.log('Login attempt failed: ' + xhr.responseText);
}
that.displayError('Username or password not recognised');
$.fancybox.close();
}
});
As you can see, the createSession method is immediately followed by a call to redirectToOrigin() - Although not shown, suffice it to say that the method redirects the page to the root. The reason this fails is simple - As soon as the initial create call is run through the redirect occurs so there is never time for any callbacks to be made.