Angular issue with asynchronous call to function

混江龙づ霸主 提交于 2020-01-17 05:38:18

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!