问题
I'm working with an Angular directive
which loads JSON data from localStorage
.
This was easily done with localStorage.getItem(id)
, but now I am trying to make it work via an API call (which in turn pulls from a database).
I have the Angular factory code working, and the http requests working, but in the directive code there's a _handleAsyncLoad()
function which is throwing me. In other words, I'm trying to use the built-in promise for the serialized
object coming back from the API layer.
ex/ I've written the new anonymous function, _getItemFromAPI:
, but unsure if I need to use the _handleAsyncLoad:
function. If not, what's the best way to ensure my serialized
object is populated with data prior to returning it.
angular.module('ui.dashboard')
.factory('DashboardState', ['$log', '$q', 'dashboardcontext', '$rootScope', function ($log, $q, dashboardcontext, $rootScope) {
function DashboardState(storage, id, hash, widgetDefinitions, stringify) {
this.storage = storage;
this.id = id;
this.hash = hash;
this.widgetDefinitions = widgetDefinitions;
this.stringify = stringify;
}
DashboardState.prototype = {
load: function (dashboardId) {
if (!this.storage) {
return null;
}
var serialized;
// fetch dashboard layout from storage
if (dashboardId != null && dashboardId != undefined) {
//serialized = this.storage.getItem(dashboardId); // OLDER, SIMPLER WAY
serialized = this._getItemFromAPI($rootScope); // NEW WAY, PULL DATA VIA API !
}
else {
// revert to original line; see dashboardOptions to main-controller
serialized = this.storage.getItem(this.id);
}
if (serialized) {
// check for promise
if (angular.isObject(serialized)) {
return this._handleAsyncLoad(serialized);
}
// otherwise handle synchronous load
return this._handleSyncLoad(serialized);
} else {
return null;
}
},
_getItemFromAPI: function ($rootscope) {
// SERVER-SIDE API CALL TO PERSIST DASHBOARD TO STORAGE - 09/03/2015 BM:
var sid = $rootScope.rageSessionVars.sessionID;
var userid = $rootScope.rageSessionVars.userID;
var dashboardId = this.id;
dashboardcontext.getDashboardImage(sid, userid, dashboardId).then(function (data) {
if (data.status == "FAIL") {
window.alert("Failed to retrieve dashboard. " + data.messages);
return false;
}
else {
return data;
}
});
return new Promise(function (resolve, reject) { });
},
_handleSyncLoad: function (serialized) {
var deserialized, result = [];
if (!serialized) {
return null;
}
if (this.stringify) {
try { // to deserialize the string
deserialized = JSON.parse(serialized);
} catch (e) {
// bad JSON, log a warning and return
$log.warn('Serialized dashboard state was malformed and could not be parsed: ', serialized);
return null;
}
}
else {
deserialized = serialized;
}
// Cache widgets
var savedWidgetDefs = deserialized.widgets;
return result;
},
_handleAsyncLoad: function (promise) {
var self = this;
var deferred = $q.defer();
promise.then(
// success
function (res) {
var result = self._handleSyncLoad(res);
if (result) {
deferred.resolve(result);
} else {
deferred.reject(result);
}
},
// failure
function (res) {
deferred.reject(res);
}
);
return deferred.promise;
}
};
return DashboardState;
}]);
databoardcontext
factory code:
function getDashboardImage(sid, userid, id) {
var rageVars = $rootScope.rageSessionVars;
var url = "http://" + rageVars.domainName + ":" + rageVars.port + "/api/dashboards";
var sid = rageVars.sessionID;
var apiCall = "getDashboardImage";
var cssClass = "html";
var req = {
method: 'POST',
url: url,
headers: {
'Content-Type': 'application/json', // application/x-www-form-urlencoded
},
data: { sid: sid, apiCall: apiCall, userid: userid, id: id }
};
var deferred = $q.defer();
deferred.notify("Retrieving dashboard image...");
$http(req).success(function (data, status, headers, config) {
deferred.resolve(data);
}).error(function (data, status, headers, config) {
console.log('Error retrieving dashboard ');
deferred.resolve();
});
return deferred.promise;
}
******** UPDATE Sept 8, 2015 2:55pm: Thanks to the gent who provide the answer, I'm posting some updated code to show what is now working. ********
angular.module('ui.dashboard')
.factory('DashboardState', ['$log', '$q', 'dashboardcontext', '$rootScope', function ($log, $q, dashboardcontext, $rootScope) {
var that = this; // *** CREATED NEW OBJECT HERE. REASSIGN BELOW IN load: FUNCTION ***
function DashboardState(storage, id, hash, widgetDefinitions, stringify) {
this.storage = storage;
this.id = id;
this.hash = hash;
this.widgetDefinitions = widgetDefinitions;
this.stringify = stringify;
}
DashboardState.prototype = {
save: function (widgets) {
/// SAVE CODE OMITTED FOR BREVITY
},
load: function (dashboardId) {
var useLocalStorage = false; // retrieve from localStorage or via API layer - 09/04/2015 BM:
var serialized;
if (useLocalStorage) { // retrieve dashboard layout from localStorage
if (!this.storage) {
return null;
}
if (dashboardId != null && dashboardId != undefined) {
serialized = this.storage.getItem(dashboardId);
// save the current dashboard id for next load
this.storage.setItem("defaultDashboardId", dashboardId);
}
}
else {
if (dashboardId != null && dashboardId != undefined) {
this.storage.setItem("defaultDashboardId", dashboardId);
that = this; // **** VERY IMPORTANT TO REASSIGN ***
// *** RETURN IS VERY IMPORTANT HERE, AS WELL AS THE then() SECTION ***
return this._getItemFromAPI($rootScope).then(function (data) {
return that._handleSyncLoad(data, true); // *** that. IS NOW AVAILABLE ON THE SCOPE ***
});
}
else {
// revert to original line; see dashboardOptions to main-controller
serialized = this.storage.getItem(this.id);
}
}
if (serialized) {
// check for promise
if (angular.isObject(serialized)) {
return this._handleAsyncLoad(serialized);
}
// otherwise handle synchronous load
return this._handleSyncLoad(serialized);
} else {
return null;
}
},
_getItemFromAPI: function ($rootscope) {
// SERVER-SIDE API CALL TO PERSIST DASHBOARD TO STORAGE - 09/03/2015 BM:
var sid = $rootScope.rageSessionVars.sessionID;
var userid = $rootScope.rageSessionVars.userID;
var dashboardId = this.id;
return dashboardcontext.getDashboardImage(sid, userid, dashboardId).then(function (data) {
return data.data[0];
});
},
_handleSyncLoad: function (serialized, isParsed) {
// @serialized {JSON} - parsed or unparsed Json object
// @isParsed {Boolean} - false if loaded from localStorage.getItem(); true if loaded from API, Json string already parsed.
var deserialized, result = [];
if (!serialized) {
return null;
}
if (isParsed) { // JSON already deserialzed in load: above; see _getItemFromAPI().then data object - 09/04/2015 BM:
deserialized = serialized;
}
else {
if (this.stringify) {
try { // to deserialize the string
deserialized = JSON.parse(serialized);
} catch (e) {
// bad JSON, log a warning and return
$log.warn('Serialized dashboard state was malformed and could not be parsed: ', serialized);
return null;
}
}
else {
deserialized = serialized;
}
}
// check hash against current hash
if (deserialized.hash !== this.hash) {
$log.info('Serialized dashboard from storage was stale (old hash: ' + deserialized.hash + ', new hash: ' + this.hash + ')');
this.storage.removeItem(this.id);
return null;
}
// Cache widgets
var savedWidgetDefs = deserialized.widgets;
// instantiate widgets from stored data
for (var i = 0; i < savedWidgetDefs.length; i++) {
// deserialized object
var savedWidgetDef = savedWidgetDefs[i];
// widget definition to use
var widgetDefinition = this.widgetDefinitions.getByName(savedWidgetDef.name);
// check for no widget
if (!widgetDefinition) {
// no widget definition found, remove and return false
$log.warn('Widget with name "' + savedWidgetDef.name + '" was not found in given widget definition objects');
continue;
}
// check widget-specific storageHash
if (widgetDefinition.hasOwnProperty('storageHash') && widgetDefinition.storageHash !== savedWidgetDef.storageHash) {
// widget definition was found, but storageHash was stale, removing storage
$log.info('Widget Definition Object with name "' + savedWidgetDef.name + '" was found ' +
'but the storageHash property on the widget definition is different from that on the ' +
'serialized widget loaded from storage. hash from storage: "' + savedWidgetDef.storageHash + '"' +
', hash from WDO: "' + widgetDefinition.storageHash + '"');
continue;
}
// push instantiated widget to result array
result.push(savedWidgetDef);
}
return result;
},
_handleAsyncLoad: function (promise) {
// code same as original post...
}
};
return DashboardState;
}]);
回答1:
As soon as you have async operation, you should make the public API async as well, even if in some or most cases, it is sync. Then the API to the user is consistent.
Instead of "checking for promise", convert the sync call to a promise.
Your load
function could then be simplified to the following (I'm leaving some details for brevity, but you should understand the broader concept):
load: function(dashboardId) {
if (isSync()){
// sync
var data = this.storage.getItem(dashboardId);
// wrap in a promise
return $q.resolve(_handleSyncLoad(data));
} else {
// async
// return the promise generated by _getItemFromAPI().then()
return _getItemFromAPI().then(function(data){
return _handleSyncLoad(data);
});
}
}
Note, that I assumed that _getItemFromAPI()
returns a promise (in your case it doesn't), so it would something like the following:
function _getItemFromAPI(){
// ...
// this "return" is important! it returns the promise generated by $http
return $http({...}).then(function(response){
return response.data;
})
}
This makes the usage of load
consistent whether it's sync or async:
dashboardSvc.load(444).then(function(dashboardData){
$scope.dashboard = dashboardData;
});
来源:https://stackoverflow.com/questions/32406163/angular-issue-with-asynchronous-call-to-function