【问题标题】:AngularJS with global $http error handling具有全局 $http 错误处理的 AngularJS
【发布时间】:2016-05-25 06:16:17
【问题描述】:

我想全局拦截某些$http 错误场景,防止控制器自己处理错误。我认为我需要一个 HTTP 拦截器,但我不确定如何让我的控制器也处理错误。

我有一个这样的控制器:

function HomeController($location, $http) {
    activate();

    function activate() {
        $http.get('non-existent-location')
            .then(function activateOk(response) {
                alert('Everything is ok');
            })
            .catch(function activateError(error) {
                alert('An error happened');
            });
    }
}

还有一个这样的 HTTP 拦截器:

function HttpInterceptor($q, $location) {
    var service = {
        responseError: responseError
    };

    return service;

    function responseError(rejection) {
        if (rejection.status === 404) {
            $location.path('/error');
        }
        return $q.reject(rejection);
    }
}

这在浏览器重定向到“/error”路径时有效。但是HomeController 中的承诺捕获也正在执行,我不希望这样。

我知道我可以对HomeController 进行编码,使其忽略 404 错误,但这是不可维护的。假设我修改 HttpInterceptor 以处理 500 个错误,然后我必须再次修改 HomeController(以及可能已经添加的任何其他使用 $http 的控制器)。有没有更优雅的解决方案?

【问题讨论】:

  • 我自己也想知道,但后来忘记了。感谢您提醒我弄清楚:D

标签: angularjs


【解决方案1】:

选项 1 - 中断/取消承诺链

HttpInterceptor 中的一个小改动可能会破坏/取消承诺链,这意味着控制器上的 activateOk 或 activateError 都不会被执行。

function HttpInterceptor($q, $location) {
    var service = {
        responseError: responseError
    };

    return service;

    function responseError(rejection) {
        if (rejection.status === 404) {
            $location.path('/error');
            return $q(function () { return null; })
        }
        return $q.reject(rejection);
    }
}

return $q(function () { return null; }) 行,取消了承诺。

这是否“可以”是一个争论的话题。 “You don't know JS”中的凯尔·辛普森说:

许多 Promise 抽象库都提供了取消的功能 承诺,但这是一个糟糕的主意!许多开发者希望 Promise 本来就设计有外部取消功能,但是 问题是它会让 Promise 的一个消费者/观察者 影响其他消费者遵守相同 Promise 的能力。 这违反了未来价值的可信赖性(外部不变性), 更是“远距离行动”的体现 反模式...

好吗?坏的?正如我所说,这是一个有争议的话题。我喜欢它不需要更改任何现有的$http 消费者这一事实。

Kyle 说得很对:

许多 Promise 抽象库都提供了取消 Promise 的功能...

例如,Bluebird Promise 库支持取消。来自the documentation:

新的取消具有“无关”语义,而旧的 取消具有中止语义。取消承诺只是意味着 不会调用它的处理程序回调。

选项 2 - 不同的抽象

Promise 是一个相对宽泛的抽象。来自Promises/A+ specification:

promise 表示异步操作的最终结果。

Angular $http 服务使用承诺的$q 实现来为异步 HTTP 请求的最终结果返回一个承诺。

$http 有 two deprecated functions、.success 和 .error 是毫无价值的,它们装饰了返回的承诺。这些函数已被弃用,因为它们不能以典型的 Promise 方式链接,并且被认为没有作为“HTTP 特定”函数集增加太多价值。

但这并不是说我们不能制作我们自己的 HTTP 抽象/包装器,它甚至不暴露 $http 使用的底层承​​诺。像这样:

function HttpWrapper($http, $location) {
    var service = {
        get: function (getUrl, successCallback, errorCallback) {
            $http.get(getUrl).then(function (response) {
                successCallback(response);
            }, function (rejection) {
                if (rejection.status === 404) {
                    $location.path('/error');
                } else {
                    errorCallback(rejection);
                }
            });
        }
    };

    return service;
}

由于这不返回一个承诺,它的消费也需要有一点不同:

HttpWrapper.get('non-existent-location', getSuccess, getError);

function getSuccess(response) {
    alert('Everything is ok');
}

function getError(error) {
    alert('An error happened');
}

在 404 的情况下,位置更改为“错误”,并且不会执行 getSuccess 和 getError 回调。

此实现意味着链接 HTTP 请求的能力不再可用。这是一个可以接受的妥协吗?结果可能会有所不同...

选项 3 - 装饰拒绝

感谢 TJ 的评论:

如果您需要在特定控制器中进行错误处理,您将需要 检查是否已处理错误的条件 拦截器/服务等

HTTP 拦截器可以使用属性handled 来装饰拒绝承诺,以表明它是否处理了错误。

function HttpInterceptor($q, $location) {
    var service = {
        responseError: responseError
    };

    return service;

    function responseError(rejection) {
        if (rejection.status === 404) {
            $location.path('/error');
            rejection.handled = true;
        }

        return $q.reject(rejection);
    }
}

控制器看起来像这样:

$http.get('non-existent-location')
    .then(function activateOk(response) {
        alert('Everything is ok');
    })
    .catch(function activateError(error) {
        if (!error.handled) {
            alert('An error happened');
        }
    });

总结

与选项 2 不同,选项 3 仍然为任何$http 消费者提供链接承诺的选项,这是一个积极的方面,因为它不会消除功能。

选项 2 和 3 的“远距离行动”都较少。在选项 2 的情况下,替代抽象清楚地表明事情的行为与通常的 $q 实现不同。对于选项 3,消费者仍会收到随心所欲的承诺。

所有 3 个选项都满足可维护性标准,因为更改全局错误处理程序以处理或多或少的场景不需要更改使用者。

【讨论】:

  • 不错。写得很好
  • 我喜欢你的短臂。
【解决方案2】:

将$http 包装在您自己的服务中。这样,如果您需要更改错误处理逻辑,则无需更改所有控制器。

类似:

angular.module('test')
  .factory('http', ['$http',
      function(http) {
        return {
          get: function(getUrl) {
            return http.get(getUrl).then(function(response) {
              return response;
            }, function() {
              //handle errors here
            });
          },
          post: function(postUrl, data) {
              return http.post(postUrl, data).then(function(response) {
                return response;
              }, function() {
                //handle errors here
              });
            }
            // other $http wrappers
        };
      });

【讨论】:

  • 在我的示例中,如果我用$http 替换htt,页面会显示“一切正常”的警报,并重定向到错误页面。 activateOk 函数的response 参数是undefined。这不是一个有利的结果。
  • @Snixtor 您的示例遇到了无效的 url,因此它永远不会调用 activateOk。被调用的将是activateError。我的回答不是复制粘贴解决方案。有一条评论//handle errors here,您需要添加您的逻辑,例如忽略拦截器处理的404 的逻辑。一旦你有了这样的服务,你可能不需要控制器中的错误处理程序,如果你需要一个,那么你应该返回错误响应。这取决于您填写。
  • 抱歉,我不确定您所说的“无效示例”是什么意思。我描述的结果是如果我将$location.path('/error'); 代替“在此处处理错误”会发生什么。除非在“在此处处理错误”中有拒绝或解决承诺,否则承诺将在 response 未定义的情况下解决。
  • @Snixtor 我的意思是无效的“url”,这是我稍后修复的错字。该服务不是拦截器的替代品。 $location.path('/error'); 应该在拦截器中,除非您出于某种原因需要移动它。该服务是处理您要在控制器的错误处理程序中添加的逻辑的常用位置。 “除非有一个承诺拒绝”我相信你可以做到这一点,或者简单地在错误处理程序中返回错误响应。
  • 感谢您的澄清,我明白您现在所说的“一旦您拥有这样的服务,您可能不需要控制器中的错误处理程序”是什么意思。但是我确实仍然希望在控制器中为除 404 之外的每个错误提供一个错误处理程序。我的问题不仅仅是关于功能,而是关于可维护性。如前所述,我可以让控制器忽略 404 错误,但这意味着控制器是根据拦截器的功能设计的。如果我修改拦截器以忽略 404 错误并处理 500,我也需要修改控制器。不好。
【解决方案3】:

This article 解释了如何拦截 http 调用并对其执行不同的操作。其中之一是处理错误。

从上面的文章中快速复制粘贴...

app.config(function($httpProvider) {
  $httpProvider.interceptors.push(function($q) {
    return {
      // This is the responseError interceptor
      responseError: function(rejection) {
        if (rejection.status > 399) { // assuming that any code over 399 is an error
          $q.reject(rejection)
        }

        return rejection;
      }
    };
  });
});

【讨论】:

  • 在这种情况下使用userService 是一项有趣的研究,它基本上是将$http 承诺的拒绝推迟到它可以成功解决(用户登录)。但除此之外,它只是拒绝承诺并依靠控制器来处理错误。这并没有解决我想要完成的事情。
  • 我假设您从文章和代码中汲取了精华。我可以删除与处理错误无关的代码
  • 我确实掌握了这篇文章的精髓,但它并没有解决我所描述的场景。文章的标题其实挺贴切的:“80/20指南”。我认为我的情况是 20%,而不是 80% :)
  • 我有点不确定你在追求什么?你想停止执行承诺链吗?我发现这个答案有点相关stackoverflow.com/a/25976060/582061
  • -1 因为我不确定这段代码应该做什么。如果你调用$q.reject,它不会做任何事情,因为$q.reject 返回一个新的promise,而这对返回值没有任何作用。此外,responseError 永远不会为 399 下的任何内容调用,因此 if 语句应始终运行。最后return rejection 将返回错误响应值作为一个成功解决的promise 到底层代码......这将使该代码将错误对象作为成功的promise 结果接收到混淆.
【解决方案4】:
application= angular.module('yourmodule', yourdependencies)  ;    
 application.config(["$provide", "$httpProvider", 
         function (provide, httpProvider){    
           registerInterceptors(provide, httpProvider);
        }]);

 registerhttpInterceptor = function (provide, httpProvider) {
                provide.factory("appHttpInterceptor", [
                    function () {
                        return {
                            responseError: function (rejection) {
                                if (rejection.status == 401) {
                                   return "Your custom response";
                                }
                            }
                        };
                    }
                ]);
                httpProvider.interceptors.push('appHttpInterceptor');
            };

【讨论】:

  • 我认为您还没有完全理解我想要实现的目标,从responseError 返回“您的自定义响应”意味着错误处理仍然是控制器的责任。
  • 不,这段代码可以全局存在,你是绑定到模块上的,你只需要注入“$provide”和“$httpProvider”并在模块初始化时运行
  • 本地与全球的存在不是当前的问题。您的示例将意味着控制器中的 $http 承诺通过对象“您的自定义响应”解析,然后由 controller 负责处理。我不希望控制器负责做任何与错误有关的事情。换个角度来看,activateOk 和 activateError 都不应该被执行。
  • 您可以调用您的 activateok 或 activateError 方法,而不是返回“您的自定义响应”。我不确定您是否正在寻找相同的,如果不是,对不起
  • 另外注意,这个方法会在控制器解析 $http 承诺之前被触发
【解决方案5】:

试试这个

工厂/golbalErrorHandler.js

yourmodule.factory('golbalErrorHandler', [function() {  
        var golbalError= {
            responseError: function(response) {
                // Golbal Error Handling
                if (response.status != 200){
                    console.error(response);
                }
            }
        }
        return golbalError;

        }]);

app.js

   yourmodule.config(['$httpProvider', function($httpProvider) {  
       $httpProvider.interceptors.push('golbalErrorHandler');
    }]);

【讨论】:

  • 我是 Angular 的新手,返回值 golbalError 有什么用?
猜你喜欢
  • 2019-10-12
  • 2012-08-11
  • 2014-06-22
  • 1970-01-01
  • 2015-08-04
  • 1970-01-01
  • 2023-04-01
  • 2010-11-25
  • 1970-01-01
相关资源
最近更新 更多