一尘不染

具有全局$ http错误处理的AngularJS

angularjs

我想全局拦截某些$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);
    }
}

这与浏览器重定向到“ /错误”路径一样有效。但在承诺抓HomeController 执行了,我不希望这样。

我知道我可以编写代码HomeController,使其忽略404错误,但这是无法维护的。假设我进行修改HttpInterceptor以同时处理500个错误,那么我将不得不HomeController再次进行修改(以及以后可能添加的其他所有使用的控制器$http)。有没有更优雅的解决方案?


阅读 230

收藏
2020-07-04

共1个答案

一尘不染

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

的微小变化HttpInterceptor可以破坏/取消承诺链,这意味着将不执行activateOkactivateError不执行控制器。

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; }),取消承诺。

这是否“可以”是一个争论的话题。“ 您不了解JS ”中的Kyle
Simpson 指出:

许多Promise抽象库都提供了取消Promises的工具,但这是一个糟糕的主意!许多开发人员希望Promises本身具有外部取消功能,但是问题在于,这将使Promise的一个消费者/观察者影响其他消费者观察该Promise的能力。这违反了未来价值的可信赖性(外部不变性),但更体现了“远距离行动”反模式的体现。

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

凯尔说的很对:

许多Promise抽象库提供了取消Promises的工具。

例如,Bluebird
Promise库支持取消。从文档中

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

选项2-不同的抽象

承诺是一个相对广泛的抽象。根据Promises / A +规范

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

Angular $http服务使用$qpromise 的实现为异步HTTP请求的最终结果返回promise。

这是值得什么,$http2个废弃的函数.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来装饰Promise拒绝,以指示它是否处理了错误。

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,消费者仍将根据自己的喜好做出承诺。

这三个选项均满足可维护性标准,因为更改全局错误处理程序以处理或多或少的情况不需要更改使用者。

2020-07-04