dva 中使用 axios 统一拦截错误示例

dva 官网使用的网络请求库是 dva/fetch,个人比较喜欢 axios,因为可以跨域,各种拦截使用起来也很舒服。项目中经常需要对错误的请求进行统一拦截,统一友好的输出错误提示。本例为实践摸索出来的一套方案,无论是在 react 还是 vue 中都可以进行参考。

1. 代码示例

先贴代码,下面有各个部分详细解释。

// request.js
import axios from 'axios';
import NProgress from 'nprogress';
import { notification, message } from 'antd';
import { routerRedux } from 'dva/router';
import store from '../index';

/**
 * 一、功能:
 * 1. 统一拦截http错误请求码;
 * 2. 统一拦截业务错误代码;
 * 3. 统一设置请求前缀
 * |-- 每个 http 加前缀 baseURL = /api/v1,从配置文件中获取 apiPrefix
 * 4. 配置异步请求过渡状态:显示蓝色加载条表示正在请求中,避免给用户页面假死的不好体验。
 * |-- 使用 NProgress 工具库。
 * 
 * 二、引包:
 * |-- axios:http 请求工具库
 * |-- NProgress:异步请求过度条,在浏览器主体部分顶部显示蓝色小条
 * |-- notification:Antd组件 > 处理错误响应码提示信息
 * |-- routerRedux:dva/router对象,用于路由跳转,错误响应码跳转相应页面
 * |-- store:dva中对象,使用里面的 dispatch 对象,用于触发路由跳转
 */

// 设置全局参数,如响应超市时间,请求前缀等。
axios.defaults.timeout = 5000
axios.defaults.baseURL = '/api/v1';
axios.defaults.withCredentials = true;

// 状态码错误信息
const codeMessage = {
  200: '服务器成功返回请求的数据。',
  201: '新建或修改数据成功。',
  202: '一个请求已经进入后台排队(异步任务)。',
  204: '删除数据成功。',
  400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
  401: '用户没有权限(令牌、用户名、密码错误)。',
  403: '用户得到授权,但是访问是被禁止的。',
  404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
  406: '请求的格式不可得。',
  410: '请求的资源被永久删除,且不会再得到的。',
  422: '当创建一个对象时,发生一个验证错误。',
  500: '服务器发生错误,请检查服务器。',
  502: '网关错误。',
  503: '服务不可用,服务器暂时过载或维护。',
  504: '网关超时。',
};

// 添加一个请求拦截器,用于设置请求过渡状态
axios.interceptors.request.use((config) => {
  // 请求开始,蓝色过渡滚动条开始出现
  NProgress.start();
  return config;
}, (error) => {
  return Promise.reject(error);
});

// 添加一个返回拦截器
axios.interceptors.response.use((response) => {
  // 请求结束,蓝色过渡滚动条消失
  NProgress.done();
  return response;
}, (error) => {
  // 请求结束,蓝色过渡滚动条消失
  // 即使出现异常,也要调用关闭方法,否则一直处于加载状态很奇怪
  NProgress.done();
  return Promise.reject(error);
});

export default function request (opt) {
  // 调用 axios api,统一拦截
  return axios(opt)
    .then((response) => 
      // >>>>>>>>>>>>>> 请求成功 <<<<<<<<<<<<<<
      console.log(`【${opt.method} ${opt.url}】请求成功,响应数据:%o`, response);

      // 打印业务错误提示
      if (response.data && response.data.code != '0000') {
        message.error(response.data.message);
      }

      return { ...response.data };
    })
    .catch((error) => {
      // >>>>>>>>>>>>>> 请求失败 <<<<<<<<<<<<<<
      // 请求配置发生的错误
      if (!error.response) {
        return console.log('Error', error.message);
      }

      // 响应时状态码处理 
      const status = error.response.status;
      const errortext = codeMessage[status] || error.response.statusText;
      
      notification.error({
        message: `请求错误 ${status}`,
        description: errortext,
      });
      
      // 存在请求,但是服务器的返回一个状态码,它们都在2xx之外
      const { dispatch } = store;

      if (status === 401) {
        dispatch(routerRedux.push('/user/login'));
      } else if (status === 403) {
        dispatch(routerRedux.push('/exception/403'));
      } else if (status <= 504 && status >= 500) {
        dispatch(routerRedux.push('/exception/500'));
      } else if (status >= 404 && status < 422) {
        dispatch(routerRedux.push('/exception/404'));
      }

      // 开发时使用,上线时删除
      console.log(`【${opt.method} ${opt.url}】请求失败,响应数据:%o`, error.response);

      return { code: status, message: errortext }; 
    });
}

2. 明确响应体

以微信小程序为例,请求响应数据分为两部分:

  • 网络请求是否成功;
  • 业务场景值。即便网络请求成功了,业务处理上可能有时也会出错,比如校验不通过。

我们在拦截响应时要分别对这两部分进行处理。

response = {
  status: 200,                // 网络请求状态。
  statusText: 'xxx',
  data: {
    code: '1001',             // 业务请求状态。这里 '0000' 表示业务没问题,其它都有问题
    message: 'yyy',
    data: {  },
  }
}

3. 依赖包分析

import axios from 'axios';
import NProgress from 'nprogress';
import { notification, message } from 'antd';
import { routerRedux } from 'dva/router';
import store from '../index';

上面几个都是比较常见的包,最后一个 import store from '../index'; 可能有些同学会比较陌生,这是 dva 中导出的对象。即下面代码最终导出的 app._store,引入它是因为 dispatch 对象在里面,我们需要 dispatch 对象进行路由跳转。

// index.js
import dva from 'dva';
import { message } from 'antd';
import { createBrowserHistory as createHistory } from 'history';

// 1. Initialize
const app = dva({
  history: createHistory(),
});

// 2. Plugins
app.use(createLoading());

// 3. Model
app.model(require('./models/app/global').default);

// 4. Router
app.router(require('./router').default);

// 5. Start
app.start('#root');

export default app._store;

4. axios 全局配置

// 设置全局参数,如响应超市时间,请求前缀等。
axios.defaults.timeout = 5000
axios.defaults.baseURL = '/api/v1';
axios.defaults.withCredentials = true;

axios 可以设置很多全局配置,具体可参阅:更多

5. 加载 NProgress 过渡组件

// 添加一个请求拦截器,用于设置请求过渡状态
axios.interceptors.request.use((config) => {
  // 请求开始,蓝色过渡滚动条开始出现
  NProgress.start();
  return config;
}, (error) => {
  return Promise.reject(error);
});

// 添加一个返回拦截器
axios.interceptors.response.use((response) => {
  // 请求结束,蓝色过渡滚动条消失
  NProgress.done();
  return response;
}, (error) => {
  // 请求结束,蓝色过渡滚动条消失
  // 即使出现异常,也要调用关闭方法,否则一直处于加载状态很奇怪
  NProgress.done();
  return Promise.reject(error);
});

NProgress 的使用主要有两个方法,当调用 NProgress.start(); 时在浏览器顶部就会出现蓝色小条,当调用 NProgress.done(); 蓝色小条就会消失。我们分别在请求开始和接收到响应调用这两个方法。

nprogress

6. 网络请求成功处理

.then((response) => 
      // >>>>>>>>>>>>>> 请求成功 <<<<<<<<<<<<<<
      console.log(`【${opt.method} ${opt.url}】请求成功,响应数据:%o`, response);

      // 打印业务错误提示
      if (response.data && response.data.code != '0000') {
        message.error(response.data.message);
      }

      return { ...response.data };
    })

网络请求状态码为 200-300 表示成功,此时还应该判断业务处理是否成功。这个根据具体项目具体规定,比如微信小程序有一套场景值。在实际项目中可以自行规定 code = '0000' 业务处理完全没问题,code = '1111' 校验不通过,code = '2222' 数据库出错等等。

最后别忘了要返回具体对象 { ...response.data }

微信小程序场景值

7. 网络请求失败处理

// 状态码错误信息
const codeMessage = {
  200: '服务器成功返回请求的数据。',
  201: '新建或修改数据成功。',
  202: '一个请求已经进入后台排队(异步任务)。',
  204: '删除数据成功。',
  400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
  401: '用户没有权限(令牌、用户名、密码错误)。',
  403: '用户得到授权,但是访问是被禁止的。',
  404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
  406: '请求的格式不可得。',
  410: '请求的资源被永久删除,且不会再得到的。',
  422: '当创建一个对象时,发生一个验证错误。',
  500: '服务器发生错误,请检查服务器。',
  502: '网关错误。',
  503: '服务不可用,服务器暂时过载或维护。',
  504: '网关超时。',
};

// ...........

.catch((error) => {
      // >>>>>>>>>>>>>> 请求失败 <<<<<<<<<<<<<<
      // 请求配置发生的错误
      if (!error.response) {
        return console.log('Error', error.message);
      }

      // 响应时状态码处理 
      const status = error.response.status;
      const errortext = codeMessage[status] || error.response.statusText;
      
      notification.error({
        message: `请求错误 ${status}`,
        description: errortext,
      });
      
      // 存在请求,但是服务器的返回一个状态码,它们都在2xx之外
      const { dispatch } = store;

      if (status === 401) {
        dispatch(routerRedux.push('/user/login'));
      } else if (status === 403) {
        dispatch(routerRedux.push('/exception/403'));
      } else if (status <= 504 && status >= 500) {
        dispatch(routerRedux.push('/exception/500'));
      } else if (status >= 404 && status < 422) {
        dispatch(routerRedux.push('/exception/404'));
      }

      // 开发时使用,上线时删除
      console.log(`【${opt.method} ${opt.url}】请求失败,响应数据:%o`, error.response);

      return { code: status, message: errortext }; 
    });

网络请求失败,首先需要根据 status 打印提示消息,告诉用户为什么请求失败。如响应码为 401,那么提示用户的文字就会是 用户没有权限(令牌、用户名、密码错误)。

对于常见的几个 status,要进行相应路由操作,跳转到不同页面以友好的方式提示用户。这些错误页面参考 Ant Design Pro

如果是 401 错误,表示用户没有权限访问或者用户名密码输入错误,应该跳转到登录页面:dispatch(routerRedux.push('/user/login'));

403:无权访问
404:访问页面不存在
500:服务器错误
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • error code(错误代码)=0是操作成功完成。error code(错误代码)=1是功能错误。error c...
    Heikki_阅读 3,356评论 1 9
  • error code(错误代码)=2000是无效的像素格式。error code(错误代码)=2001是指定的驱动...
    Heikki_阅读 1,766评论 0 4
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 初看题目,亲们可能会以为即将开启一段让人心旷神怡的虐恋。呵呵,你想的太复杂而简单了。其实我要说这事...... 作...
    悟空很忙阅读 419评论 0 1
  • 达鲁花赤阅读 62评论 0 0