I am trying to use a service
that runs an asynchronous function.
I am trying to call the factory, then do something only it is resolved.
But it doesn't work, I get the error : Uncaught TypeError: Cannot read property 'then' of undefined
I am declaring the deferred object into the service function and I return its promise.
Can you help me please ?
app.js:
angular.module('SnowBoard', ['ionic', 'ngCookies', 'ui.unique', 'SnowBoard.controllers', 'SnowBoard.services'])
.run(["isPhoneGap","connectionStatus", 'updateProDB', "$ionicPlatform", '$q', 'sessionService', 'imagesService', function(isPhoneGap, connectionStatus, updateProDB, $ionicPlatform, $q, sessionService, imagesService) {
$ionicPlatform.ready(function() {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if(window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if(window.StatusBar) {
// org.apache.cordova.statusbar required
StatusBar.styleDefault();
}
});
var promise = updateProDB.get();
promise.then(
function(data) {
imagesService.checkIfImagesExistAllAtOnce(prodata);
},
function(error) {
});
}])
service.js:
.service('updateProDB', ['isPhoneGap', 'connectionStatus', 'isIOS', '$q', 'sessionService', function updateProDBFactory(isPhoneGap, connectionStatus, isIOS, $q, sessionService) {
this.get = function(){
var debugOptionUseLocalDB=0;
var prodata=[];
var gotANewDB;
var dbReadyDeferred = $q.defer();
if (typeof debugOptionUseLocalDB != 'undefined' && debugOptionUseLocalDB) {
fallbackToLocalDBfile();
return dbReadyDeferred.promise;
gotANewDB = 1;
console.log('on a fini fallbackToLocalDBfile, et dbReadyDeferred.state()='+dbReadyDeferred.state());
}else{
if(connectionStatus == 'online'){
console.log("reaching DB on server (getJsonpFile)...");
getDBfileXHR(dbUrl()).then(function(){ //if resolved
console.log(prodata);
return dbReadyDeferred.promise;
}, function (){ // if rejected
console.log("...basic XHR request failed, falling back to local DB file...and sending mail to dev.");
fallbackToLocalDBfile();
return dbReadyDeferred.promise;
var body = "";
if ( isPhoneGap ) {
body += " Platform : phonegap";
body += " device.cordova : "+device.cordova;
body += " device.model : "+device.model;
body += " device.name : "+device.name;
body += " device.platform : "+device.platform;
body += " device.uuid : "+device.uuid;
body += " device.version : "+device.version;
} else {
body += "Platform : not phonegap -> web browser"
body += "navigator.userAgent : "+navigator.userAgent;
}
var data={
userEmail: "louisromain@yahoo.fr",
subject: "BoardLine dev issue: had to fallback to local DB file",
destEmail: "louisromain@yahoo.fr",
body: body
}
sendToServer(data).done(funcSuccess).fail(funcError);
function funcSuccess(data /* , textStatus, jqXHR */ ) {
console.log("Message to dev successfully sent");
}
function funcError(data , textStatus, jqXHR ) {
console.log("The message to dev could not be sent...");
}
});
}else{ //offline
console.log('device is offline');
if(localStorage) {
if ( isPhoneGap || !isIOS() ) { //BUG iOS safari doesn't work with this (Cf. Philippe's ipad)
if (localStorage.getItem("proDB") === null ) { //if proDB exists in localStorage
fallbackToLocalDBfile();
} else {
//popShortToast("...reading DB in localStorage.");
var data = JSON.parse(localStorage["proDB"]); //read current localstorage
prodata = storeJsonInProdata(data);
sessionService.store('prodata', prodata);
dbReadyDeferred.resolve(); //initializeSelectButtons();
return dbReadyDeferred.promise;
}
}
}else{ //if localStorage not available, read local file
prodata = fallbackToLocalDBfile();
return dbReadyDeferred.promise;
}
}
}
function getDBfileXHR(url) {
var getDBfileXHRdeferred = $q.defer();
var request = new XMLHttpRequest();
request.open("GET", url, true); //3rd parameter is sync/async
request.timeout = 2000;
//console.log(url);
request.onreadystatechange = function() { //Call a function when the state changes.
if (request.readyState == 4) {
if (request.status == 200 || request.status == 0) {
console.log('we get a response from XHR');
//popShortToast("...updating DB from server using simple XHR.");
var jsonText = request.responseText.replace("callback(", "").replace(");", "");
prodata = storeJsonInProdata(JSON.parse(jsonText));
sessionService.store('prodata', prodata);
// console.log(prodata);
gotANewDB = 1;
getDBfileXHRdeferred.resolve();
dbReadyDeferred.resolve();
} else {
console.log('error : request.status = '+request.status);
getDBfileXHRdeferred.reject();
}
}
}
console.log("Sending XMLHttpRequest...");
request.send();
return getDBfileXHRdeferred.promise;
}
function dbUrl(){
return unescape(encodeURIComponent("http://user:pass@boardlineapp.com/app/proDB.jsonp")); //JSONP
}
function fallbackToLocalDBfile(){
getDBfileXHR('proDB.jsonp').then(function(){ //if resolved
console.log(prodata);
return dbReadyDeferred.promise;
});
}
}
function sendToServer(dataToSend) {
return $.ajax({
url: 'http://aurel:aurel40@boardlineapp.com/app/mail.php',
type: "POST",
dataType: "xml",
data: dataToSend
});
}
function storeJsonInProdata(data) { //function to store the DB json file into a variable prodata usable in the whole app
console.log("storing json in prodata");
//clear prodata first
var prodata=[];
//if JSON
var lines=[];
for(var i = 0; i <= data.length; i++){
lines[i]=data[i];
}
var fieldnames=lines[0];
//if tab separated TXT with each model on a separate line
// var lines=data.split(/\n/);
// var fieldnames=lines[0].split(/\t/);
var i;
prodata.push(lines[0]);
//prodata.push(0);
for (i = 1; i < lines.length-1; ++i) {
//if JSON
var fields=lines[i];
//if TXT
// var fields=lines[i].split(/\t/);
//prodata.push(i);
var j;
prodata[i]={};
prodata[i]['id']=i; //auto id, there is no more 'id' column in the DB file.
for (j = 0; j < fields.length; ++j) {
var str=fieldnames[j];
prodata[i][str]=fields[j];
}
}
return prodata;
}
}]);
There appears to be at least one path that doesn't not return a value, nutshell is the path where:
!debugOptionUseLocalBD
&& connectionStats != 'online'
&& localStorage
&& (isPhoneGap || !sIOS())
&& localStorage.getItem("proDB") === null
The if (connectionStatus == 'online')
also appears to not return anything immediately.
I find the code a bit difficult to follow–the above may not be completely accurate.
That branch only calls fallbackToLocalDBfile()
, and there appears to be no return. (I think.)
if (typeof debugOptionUseLocalDB != 'undefined' && debugOptionUseLocalDB) {
fallbackToLocalDBfile();
gotANewDB = 1;
return dbReadyDeferred.promise;
} else {
if (connectionStatus == 'online') {
//
// !!!!!!!!!! HERE !!!!!!!!!!
//
getDBfileXHR(dbUrl()).then(function () {
console.log(prodata);
return dbReadyDeferred.promise;
}, function () {
fallbackToLocalDBfile();
// The isPhoneGap/etc. and sendToServer() call; elided for clarity
sendDevMessage();
return dbReadyDeferred.promise;
});
} else {
if (localStorage) {
if (isPhoneGap || !isIOS()) { //BUG iOS safari doesn't work with this (Cf. Philippe's ipad)
if (localStorage.getItem("proDB") === null) {
//
// !!!!!!!!!! HERE !!!!!!!!!!
//
fallbackToLocalDBfile();
} else {
var data = JSON.parse(localStorage["proDB"]);
prodata = storeJsonInProdata(data);
sessionService.store('prodata', prodata);
dbReadyDeferred.resolve();
return dbReadyDeferred.promise;
}
}
} else {
prodata = fallbackToLocalDBfile();
return dbReadyDeferred.promise;
}
}
}
Not explaining the circumstances under which the error occurs makes it difficult to help further, but it appears there's at least one set of conditions under which you get undefined
back, and it may be the one I'm highlighting here.
Again, this is more or less a stab in the dark. The code is difficult to understand, as it mixes AngularJS, jQuery (AFAICT, re: the $.ajax
call), raw XMLHttpRequests
, etc. There are a significant number of issues present that make reasoning about what's happening quite difficult, at least for me.
Louis, there's more to this that just ensuring something is returned from all possible branches.
You have to know exactly what to return from each inner function such that .get()
will ultimately return a promise of prodata
however derived.
The whole thing can, and should, be done without creating the explicit promises dbReadyDeferred
and getDBfileXHRdeferred
. They are unnecessary and the many return dbReadyDeferred.promise
statements are wrong.
Life is made slightly awkward by using jQuery.ajax()
in an angular environment. The "proper" solution would be to use $http()
in place of jQuery.ajax()
but much can be achieved (or at least attempted) by coercing jQuery promises to $q, thereby guaranteeing that .get()
always returns an angualr promise.
This heavily modified version of the service is untested and probably still needs to be debugged but should give you a good idea of what's required.
.service('updateProDB', ['isPhoneGap', 'connectionStatus', 'isIOS', '$q', 'sessionService', function updateProDBFactory(isPhoneGap, connectionStatus, isIOS, $q, sessionService) {
this.get = function() {
var debugOptionUseLocalDB = 0;
if(typeof debugOptionUseLocalDB != 'undefined' && debugOptionUseLocalDB) {
return fallbackToLocalDBfile();
} else {
if(connectionStatus == 'online') {
console.log("reaching DB on server (getJsonpFile)...");
return getDBfileXHR(dbUrl()).then(null, function () {
console.log("...basic XHR request failed, falling back to local DB file...and sending mail to dev.");
return fallbackToLocalDBfile().then(function(prodata) {
var body = [];
if (isPhoneGap) {
body.push("Platform : phonegap");
body.push("device.cordova : " + device.cordova);
body.push("device.model : " + device.model);
body.push("device.name : " + device.name);
body.push("device.platform : " + device.platform);
body.push("device.uuid : " + device.uuid);
body.push("device.version : " + device.version);
} else {
body.push("Platform : not phonegap -> web browser");
body.push("navigator.userAgent : " + navigator.userAgent);
}
var data = {
userEmail: "louisromain@yahoo.fr",
subject: "BoardLine dev issue: had to fallback to local DB file",
destEmail: "louisromain@yahoo.fr",
body: body.join(' ')
};
return sendToServer(data).then(function() {
return prodata;
}, function(textStatus) {
console.error("The message to dev could not be sent... :" + textStatus);
return textStatus;
});
});
});
} else { //offline
console.log('device is offline');
if(localStorage) {
if (isPhoneGap || !isIOS()) { //BUG iOS safari doesn't work with this (Cf. Philippe's ipad)
if (localStorage.getItem("proDB") === null ) { //if proDB doesn't exist in localStorage
return fallbackToLocalDBfile();
} else {
var data = JSON.parse(localStorage["proDB"]); //read current localstorage
var prodata = storeJsonInProdata(data);
sessionService.store('prodata', prodata);
return $q.when(prodata); //promise of prodata
}
} else {
return fallbackToLocalDBfile();
}
} else { //if localStorage not available, read local file
return fallbackToLocalDBfile();
}
}
}
function getDBfileXHR(url) {
console.log("Sending XMLHttpRequest...");
var jqXHR = $.ajax({
url: url,
dataType: 'json',
timeout: 2000
}).then(function(data) {
var prodata = storeJsonInProdata(data);
sessionService.store('prodata', prodata);
return prodata;
}, function(jqXHR, textstatus, errorThrown) {
console.error('getDBfileXHR: url=' + url + ': ' + textstatus);
return textstatus;
});
return $q.when(jqXHR);//coerce jQuery promise to angular
}
function dbUrl() {
return unescape(encodeURIComponent("http://user:pass@boardlineapp.com/app/proDB.jsonp")); //JSONP
}
function fallbackToLocalDBfile() {
return getDBfileXHR('proDB.jsonp');
}
}
function sendToServer(dataToSend) {
var jqXHR = $.ajax({
url: 'http://aurel:aurel40@boardlineapp.com/app/mail.php',
type: "POST",
dataType: "xml",
data: dataToSend
}).then(function(data, textStatus, jqXHR) {
return data;
}, function(jqXHR, textStatus, errorThrown) {
console.error('sendToServer: dataToSend=' + dataToSend + ': ' + textstatus);
return textStatus;
});
return $q.when(jqXHR);//coerce jQuery promise to angular
}
function storeJsonInProdata(lines) { //function to store the DB json file into a variable prodata usable in the whole app
var i, j;
console.log("storing json in prodata");
var prodata = [lines[0]];
for (i = 1; i < lines.length-1; ++i) {
prodata[i] = { 'id': i }; //auto id, there is no more 'id' column in the DB file.
for (j = 0; j < lines[i].length; ++j) {
prodata[i][lines[0][j]] = lines[i][j];
}
}
return prodata;
}
}]);
I've stuck with the same overall structure as the code in the question but you can (and maybe should) take things further by throwing an Error at each point fallbackToLocalDBfile(prodata)
is called and handling such errors centrally in a generalised catch
. That would be cleaner.
I also attempted to tidy up storeJsonInProdata()
, which will need testing.