简体   繁体   English

解决Angular Service中的承诺

[英]Resolving a promise in Angular Service

We have a service, lets call it AccountService which exposes a method called getAccounts(customerId) among others. 我们有一个服务,叫它AccountService ,它公开了一个名为getAccounts(customerId)的方法。

In its implementation all it does is to fire up a $http GET request and return a promise to the calling controller which will put the returned array of accounts in the controller scope once resolved. 在其实现中,它要做的就是触发$ http GET请求,并向调用控制器返回承诺,一旦解决,它将把返回的帐户数组放入控制器范围内。

On a simplified view all looks like below: 在简化视图中,所有内容如下所示:

// The service
.factory('AccountService', ['$http', function($http) {
    var _getAccounts = function(customerId) {
        var request = {
            'method': 'GET',
            'url': 'http://localhost:8081/accounts/' + customerId
        };
        return $(request);
    };

    return {
        getAccounts: _getAccounts
    };
}]);

// Inside the conntroller
AccountService.getAccounts($scope.customerId)
    .then(function(response) {
        $scope.accounts = response.data;
    });

So once the promise will resolve the controller scope will get populated with the list of accounts. 因此,一旦诺言得以解决,则控制者范围将被填充帐户列表。

Note that I kept the above code as simple as I could to get you the idea of what my problem is but in reality it will be code to deal with exceptions, watcher to refresh, etc. Everything works fine. 请注意,我尽可能简化了上面的代码,以使您了解我的问题所在,但实际上,它将是处理异常,观察者进行刷新等的代码。一切正常。

My problem is that this AccountService is used from lots of controllers and putting the promise resolve in all of these looks to me not only repeating all this boiler plate resolver code but also complicating the unit testing as I am obliged to r/test both successful and exception scenarios in every single controller test. 我的问题是,很多控制器都使用了这个AccountService,并且在所有这些外观中都使用了承诺解析,这不仅重复了所有这些样板解析器代码,而且使单元测试变得复杂,因为我必须成功地对R / test进行测试。每个控制器测试中的异常情况。

So my question is: Is there a nice way to resolve the promise in the service and return the response to the controller, not the promise? 所以我的问题是:是否有一种很好的方法来解决服务中的诺言并将响应返回给控制器,而不是诺言?

Please note I am a very beginner with Angular and JS so please be gentle if my question looks naive. 请注意,我是Angular和JS的初学者,所以如果我的问题太幼稚,请保持谦虚。 I have heaps of java experience and my mind seems to go java like everywhere which may not be the case. 我有大量的Java经验,我的想法似乎像遍地都是Java,但事实并非如此。

Thank you in advance for your inputs 预先感谢您的输入

To answer your original question: 要回答您的原始问题:

Is there a nice way to resolve the promise in the service and return the response to the controller, not the promise? 有没有一种好的方法来解决服务中的承诺并将响应返回给控制器,而不是承诺?

In my opinion, no, there isn't. 在我看来,不,没有。 It boils down to the way asynchronous calls work - you either pass a callback (and the method returns nothing), or you don't pass a callback and the method returns an object which will be notified (a promise). 归结为异步调用的工作方式-您要么传递一个回调(该方法不返回任何内容),要么不传递一个回调,并且该方法返回一个将被通知的对象(一个Promise)。 There may be some workarounds, but I don't think it gets nicer than that. 可能有一些解决方法,但是我认为没有比这更好的方法了。

One way to partially reduce the boilerplate is to use a catch in the service, and return the promise returned by it instead. 一种部分减少样板的方法是在服务中使用catch ,然后返回由它返回的承诺

Consider the following extremely simplified example: 考虑以下极其简化的示例:

angular.module('myApp')
  .factory('NetworkRequests', [
    function() {
      var _getData = function() {
        var promise = new Promise((resolve, reject) => {
          var a = true,
              data = ['a', 'b', 'c'];

          if (a) {
            resolve(data);
          } else {
            reject('Rejection reason: ...');  
          }
        });

        return promise.catch((error) => {
          // Notify some error handling service etc.
          console.log(error);
          return [];
        });
      };

      return {
        getData: _getData
      };  
    }
  ]);    

The promise variable would be the result from your http request. promise变量将是您的http请求的结果。 You should return some data in the catch function that makes sense in the controller context (eg empty array). 您应该在catch函数中返回一些在控制器上下文中有意义的数据(例如,空数组)。 Then you don't have to bother with error handling in the controller: 然后,您不必为控制器中的错误处理而烦恼:

angular.module('myApp')
  .controller('DataController', ['NetworkRequests',
    function(NetworkRequests) {
      NetworkRequests.getData().then((data) => {
        this.data = data;
      });
    }
  ]);

Again, this doesn't solve the complete issue, but at least the error handling part can be encapsulated in the service. 同样,这不能解决完整的问题,但是至少可以将错误处理部分封装在服务中。

You can design in such a way that once your $http is done with fetching the data, store it your factory variable (somewhat a cache), and for subsequent factory calls, you check if the cache has such data. 您可以这样设计:一旦$http完成了数据的获取,就将其存储为工厂变量(某种程度上是一个缓存),对于后续的工厂调用,您将检查缓存中是否包含此类数据。 If yes, return the cache data, else call the $http calls. 如果是,则返回缓存数据,否则调用$http调用。

Here is the code: 这是代码:

.factory('AccountService', ['$http', '$q', function($http, $q) {
  var cachedData = null;
  var defered = $q.defer(); //create our own defered object
  var _getAccounts = function(customerId) {
    if (cachedData !== null) {
      console.log('get from cachedData')
      defered.resolve(cachedData); // resolve it so that the data is passed outside
      return defered.promise; //return your own promise if cached data is found
    } else {
      var request = {
        'method': 'GET',
        'url': 'mockdata.json'
      };
      return $http(request).then((response) => { //return a normal $http promise if it is not.
        console.log('get from $http');
        cachedData = response.data;
        return cachedData;
      });
    }
  };

  return {
    getAccounts: _getAccounts
  };
}]);

Here is the working plnkr . 这是工作中的plnkr You can open up the console, and click the GetData button. 您可以打开控制台,然后单击“ GetData”按钮。 You will see that first time it logs get from $http , where as subsequent calls it logs get from cachedData . 您将看到它的日志第一次get from $http ,在随后的调用中,它的日志get from cachedData

One way is to reuse an object and fill it with data. 一种方法是重用对象并用数据填充它。 It is used by ngResource. ngResource使用它。

It is something like 就像

var data = [];

function getAccounts(customerId) {
    var promise = $http(...).then((response) => {
      Object.assign(promise.data, response.data)
    });

    promise.data = [];

    return promise;
};

Data is available for binding as $scope.accounts = AccountService.getAccounts(...).data . 数据可作为$scope.accounts = AccountService.getAccounts(...).data进行绑定。 The obvious drawback is that there is a splash of unloaded content. 明显的缺点是有大量的卸载内容。

Another way is the one you've mentioned. 另一种方法是您提到的方法。 It is being used most frequently. 它是最常用的。 If there is a problem with WET code in controllers, it should be treated by eliminating WET code with class inheritance, not by changing the way it works. 如果控制器中的WET代码存在问题,则应通过消除具有类继承的WET代码来解决,而不是更改其工作方式。

Yet another way is the recommended one. 另一种方法是推荐的方法。 Using a router and route/state resolvers eliminates the need for asynchronously loaded data. 使用路由器和路由/状态解析器可以消除对异步加载数据的需求。 The data resolved in resolver is injected into route template as an array. 在解析器中解析的数据作为数组注入到路由模板中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM