AngularJs中使用拦截器interceptor对全局HTTP错误进行统一处理

目录

前言

  在web开发的各种业务场景中,http请求无疑是家常便饭,任何前后端交互数据都需要通过http请求进行传输。同时我们知道,HTTP响应有很多状态码,对应着很多种不同的服务处理结果。在这种情况下,如果项目中每一处http请求中我们都要对各种响应结果做以判断的话,那么整个工程中就会出现大量重复代码,这样做会带来不少问题,比如:重复劳动,代码结构不清晰、核心逻辑不明确,一旦对错误处理的需求变更就要对每一处进行修改、费时费力且容易遗漏。
  针对这样的情况,在以AngularJs为前端框架的项目中,我们可以使用拦截器interceptor对全局HTTP错误进行统一处理。


相关背景知识

AngularJs的拦截器interceptor

  • 功能:任何时候如果我们想要为请求添加全局功能,例如身份验证、错误处理等,在请求发送给服务器之前或者从服务器返回时可以对其进行拦截。
  • 本质:$http服务的基础中间件,用来向应用的业务流程中注入新的逻辑。
  • 核心:服务工厂factory。通过向$httpProvider.interceptors数组中添加服务工厂,在$httpProvider中进行注册。

服务工厂factory()

服务提供了一种能在应用的整个生命周期内保持数据的方法,它能够在控制器之间进行通
信,并且能保证数据的一致性。服务是一个单例对象,在每个应用中只会被实例化一次(被$injector实例化),并且是延迟加载的(需要时才会被创建)。服务提供了把与特定功能相关联的方法集中在一起的接口。

服务的工厂函数用来生成一个单例的对象或函数,这个对象或函数就是服务,它会存在于应
用的整个生命周期内。当我们的AngularJS应用加载服务时,这个函数会被执行并返回一个单例
的服务对象。服务的工厂函数既可以是一个函数也可以是一个数组,比如:

1
2
3
// 用方括号声明工厂
angular.module('myApp.services', [])
.factory('githubService', [function($http) { }]);

顺便提一下,控制器controller的声明也是这样,可以是函数或数组,比如:

1
controller: function(){};


1
controller: [function() {}]

在AngularJS应用中,factory()方法是用来注册服务的最常规方式。。factory()函数可以接受两个参数。
name:(字符串),需要注册的服务名。
getFn:(函数),这个函数会在AngularJS创建服务实例时被调用。

  • 种类
    一共有四种拦截器,两种成功拦截器,两种失败拦截器。
    request
    AngularJS通过$http设置对象来对请求拦截器进行调用。它可以对设置对象进行修改,或者创建
    
    一个新的设置对象,它需要返回一个更新过的设置对象,或者一个可以返回新的设置对象的promise。
    response
    AngularJS通过$http设置对象来对响应拦截器进行调用。它可以对响应进行修改,或者创建
    
    一个新的响应,它需要返回一个更新过的响应,或者一个可以返回新响应的promise。
    requestError
    AngularJS会在上一个请求拦截器抛出错误,或者promise被reject时调用此拦截器。
    
    responseError
    AngularJS会在上一个响应拦截器抛出错误,或者promise被reject时调用此拦截器。
    

AngularJs的$httpProvider

  $httpProvider是用来修改AngularJs中$http服务的默认行为的。它有两个属性:defaults和interceptors。
  其中,defaults是一个包含了全局$http服务请求的参数值的对象,主要有这几个属性:
defaults.cache
defaults.xsrfCookieName
defaults.xsrfHeaderName
defaults.headers
defaults.paramSerializer
  而interceptors则是一个数组,是一个服务工厂的数组。它包含了所有的同步/异步$http中对请求进行预处理或者对响应进行后处理的服务工厂。

AngularJs模块加载前的配置config()

  在模块的加载阶段,AngularJS会在提供者注册和配置的过程中对模块进行配置。在整个AngularJS的工作流中,这个阶段是唯一能够在应用启动前进行修改的部分。
  config()函数接受一个参数:configFunction(函数):AngularJS在模块加载时会执行这个函数。如:

1
2
3
4
5
6
7
angular.module('myApp', [])
.config(function($routeProvider) {
$routeProvider.when('/', {
controller: 'WelcomeController',
template: 'views/welcome.html'
});
})

AngularJs的$q服务

  $q是AngularJs中的一个服务,可以用来异步执行函数,并在异步请求执行完成时使用返回的结果。$q的使用和ES6(ES2015)中的promise很类似。关于promise的学习,推荐阮一峰的ES6标准入门。关于$q服务的详细信息,参考这一篇AngularJs中$q服务(service)的使用,这里不过多介绍。
  在拦截到HTTP后,我们需要使用$q服务通过返回一个rejection来阻止下一步的默认行为。

AngularJs的$on服务

  $on用于监听事件变化,比如状态改变事件:

1
2
3
4
5
$scope.$on('$stateChangeStart',
function(evt, toState, roParams, fromState, fromParams) {
// 可以阻止这一状态完成
evt.preventDefault();
});

我们可以通过如上方法自定义监听事件,第一个字符串参数是事件名称,第二个是监听事件发生时的回调函数。我们使用这种监听,当拦截到http错误时,向下broadcast广播这个事件,从而触发一些其他事件(比如使用弹窗提醒错误信息)。关于$broadcast和$emit,参考这一篇AngularJs的$emit和$broadcast事件分析

AngularJs的运行块run()方法

  它是所有AngularJS应用中第一个被执行的方法。运行块是AngularJS中与平时所说“main()方法”最接近的概念。运行块通常用来注册全局的事件监听器。例如,我们会在.run()块中设置路由事件的监听器以及过滤未经授权的请求。假设我们需要在每次路由发生变化时,都执行一个函数来验证用户的权限,放置这个功能唯一合理的地方就是run方法。
  在拦截HTTP请求的过程中,我们在整个应用的module上注册一个这样的运行块,通过拦截,将错误信息保存到$rootScope,并由Box组件负责展示。


HTTP错误拦截器的实现

创建错误拦截服务工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
angular.module('portalApp').factory('errorInterceptor', function($q,$rootScope) {
return {
// optional method
'request' : function(config) {
return config;
},

// optional method
'response' : function(response) {
if(!response.data)
return response;
if(response.data.success==undefined)
return response;
if(response.data.success==false){
$rootScope.$broadcast("warn",{
type:'error',
desc:response.data.desc
});
}
return response;
},

// optional method
'responseError' : function(rejection) {
// do something on error
if (rejection.status == -1) {
$rootScope.$broadcast("warn",{
type:'error',
desc:'连接服务器失败'
});
return $q.reject(rejection);
}
if (rejection.status == 500) {
$rootScope.$broadcast("warn",{
type:'error',
desc:'服务器内部错误'
});
return $q.reject(rejection);
}
if (rejection.status == 404) {
$rootScope.$broadcast("warn",{
type:'error',
desc:'请求的页面不存在'
});
return $q.reject(rejection);
}
if (rejection.status == 401) {
window.location="/login.html";
}
if (rejection.status == 403) {
$rootScope.$broadcast("warn",{
type:'error',
desc:'对不起,您没有访问权限'
});
return $q.reject(rejection);
}
$rootScope.$broadcast("warn",{
type:'error',
desc:'服务器发生未知错误(status'+rejection.status+')'
});
return $q.reject(rejection);
}
};
});

向$httpProvider配置错误拦截器

1
2
3
angular.module('portalApp').config([ '$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('errorInterceptor');
}]);

注册运行块run,监听错误结果进行回调处理

1
2
3
4
5
6
7
8
9
10
angular.module('portalApp', [ 'ngComponentRouter', ...及其他依赖注入模块])
.controller(
'mainCtrl',[...此处省略...]
).run(function($rootScope,ngDialog,Box){
$rootScope.$on('warn',function(event, warnMsg){
setTimeout(function(){
Box.error(warnMsg.desc);
}, 500);
});
});

参考:《AngularJs权威教程》、AngularJs官方文档