Angular最佳实践:在Factory还是Controller中使用Promise?

3

我在我的应用程序中有一个基本的工厂,处理API调用。目前我正在使用以下形式:

.factory('apiFactory', function($http){

  var url = 'http://192.168.22.8:8001/api/v1/';

  return {
    getReports: function() {
      return $http.get(url+'reports').then(function(result) {
        return result;
      });
    },
    getReport: function(id) {
      return $http.get(url+'report/'+id).then(function(result) {
        return result;
      });
    }
  }
})

在我的控制器中,我像这样处理承诺:

.controller('exampleController', function($scope, apiFactory) {

      apiFactory.getReports().then(
        function(answer) {
          if (answer.status==200){
            if (answer.data.status == "error"){
              // DISPLAY ERROR MESSAGE
              console.log(answer.data.msg);
            }
          } else{
            // THROW error
            console.log('error: ', answer);
          }
        },
        function(error){
          console.log('error: ', answer);
        }
      );
    }
  }
})

看起来我可以将 Promise 处理移动到 Factory 而不是在我的控制器中处理,但我不确定除了更小的控制器之外还有什么好处。

有人可以解释一下关于这种模式的最佳实践吗?


工厂如何知道如何显示错误消息? - Sacho
似乎状态码检查不应该在控制器中进行,而应该在工厂中处理……?我不确定,所以想问一下 :) - Squrler
好的,但是工厂在检查错误代码后应该做什么? - Sacho
3个回答

10

最终,向服务调用者提供多少数据取决于您自己。如果需要的话,您可以将HTTP响应对象返回给调用者,并让他们处理响应(顺便说一下,如果promise被解决而不是被拒绝,则始终为HTTP 2xx)。

但是,如果您想将调用者与数据获取的具体细节隔离开来(例如数据可能已被缓存或通过其他机制提供),并且如果您需要对数据进行后处理,则建议在服务中处理响应。

以下是一个示例:

.factory("apiService", function($http, $q){
  var url = 'http://192.168.22.8:8001/api/v1/';

  return {
    getReports: function() {
      return $http.get(url+'reports').then(function(result) {
        var data = result.data;

        if (data === "something I don't accept"){
           return $q.reject("Invalid data");
        }

        var processedData = processData(data);
        return processedData;
      })
      .catch(function(err){
         // for example, "re-throw" to "hide" HTTP specifics
         return $q.reject("Data not available");
      })
    },
    // same idea for getReport
  }
});

这样一来,控制器就不需要关心底层机制了 - 它只接收数据或拒绝信息。

.controller('exampleController', function($scope, apiService) {
   apiService.getReports()
     .then(function(reports){
        $scope.reports = reports; // actual reports data
     });
})

离题:

注意我如何将服务的名称从"apiFactory"更改为"apiService"。 我想指出这一点,以消除可能存在的误解。 无论您使用.factory还是.service.value,您得到的可注入对象始终是服务实例。 .factory只是实例化此服务的机制,因此名称"apiFactory"是一个错误的称呼。 这里唯一的“工厂”是您使用.factory注册的函数(当然可以是匿名函数):

.factory("fooSvc", function fooSvcFactory(){
   return {
      getFoo: function(){...}
   }
})

很好,这就是我在寻找的指导。谢谢@new-dev! - Squrler
@newdev 这是一个非常出色的解释..+1 - Pankaj Parkar

2
简短回答:在Factory中处理promise。
为什么?
如果您在Controller中处理promise,可能会遇到以下问题:
假设您有5个使用相同Factory的控制器(Controller)。现在假设当promise没有正确解决时,您想要处理错误。那么在第一个控制器(Controller)中,您编写了一个错误回调(或者更准确地说是catch(exception),因为您正在处理promise),它会显示带有错误的警报消息。当Promise失败时,此控制器将显示带有错误消息的警报。目前为止还好吗?对。但等等!其他4个控制器怎么办?您没有在它们中处理错误。所以现在你需要复制第一个控制器的错误处理代码,并将其粘贴到剩下的4个控制器中。
现在就开始乐趣了。想象一下,如果您想要更改错误状态的逻辑。也许您只想在控制台中记录错误,或者显示一个信息提示框。因此,您进入第一个控制器并更新代码。您认为您做完了?错!!!仍然有4个其他控制器正在显示警报消息(记得之前从第一个控制器中复制代码粘贴过去吗?)。因此,现在,您尝试通过粘贴新的错误消息逻辑来更新其余控制器。如果您有更多控制器,考虑需要更新的所有控制器!累人,是不是?
为什么在Factory中解决promise问题:
现在假设您在服务(Service)中编写了错误处理逻辑,并且所有控制器(Controller)都在使用此服务。您已经编写了代码,在出现Promise失败的情况下,显示警报。所有控制器现在都会在出现破碎的Promise时显示此警报。将来,如果您希望更新错误逻辑,则只需在服务中更新此错误逻辑,而所有控制器将自动开始使用此更新后的逻辑,无需再进行任何努力。通过这种方式,您节省了大量时间。
所以请考虑:在Factory中进行1次更改与在所有Controllers中进行1次或多次更改。
我绝对会支持在Factory中进行1次更改,因为它可以帮助我仅通过简单更改一次重用我的逻辑。所有我的控制器将开始使用新逻辑。控制器应该变得轻巧、精悍和干净。Factory和Service应该是可重用的。出于这个可重用性的原因,我强烈建议您在Factory/Service中处理promise。

2
最好将所有数据获取操作放在工厂函数内部。这样可以保持控制器不受状态的影响,它不再关心工厂函数是如何运作的。如果您更改了数据获取方式(例如不使用 $http),那么您的控制器也不用关心,因为它只需调用 getReport()。
有一个很好的解释(参见“在控制器中解决模型数据和回调参数绑定”): http://toddmotto.com/rethinking-angular-js-controllers/

但这不就是我正在做的吗?实际的$http请求都发生在工厂中,而不是控制器中。 - Squrler
是的,我误读了,因为控制器正在检查状态码 :) - psimyn
啊,我明白了 :) 这实际上是我问题的一部分... 我应该在工厂中检查状态码而不是在控制器中吗? - Squrler
如果您能够尽可能地将与网络相关的内容隔离在工厂/服务中,通常会更容易维护。然后,工厂只需将数据映射到视图即可。 - psimyn
1
@psimyn,这是正确的观点,但我认为如果您提供了一个示例而不是链接到外部网站,答案将会大大改善。 - New Dev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接