数据请求是我们开发中非常重要的一环,如何优雅地进行抽象处理,不是一件很容易的事情,也是经常被忽略的事情,处理不好的话,重复的代码散落在各处,维护成本极高。

所以我们需要好好梳理下数据请求涉及到哪些方面,对它有整体的管控,从而设计出扩展性高的方案。

案例分析

下面我们以 axios 这个请求库进行讲解。

假如我们在页面中发出一个 POST 请求,类似这样:

axios.post('/user/create', { name: 'beyondxgb' }).then((result) => {// do something
});

后来发现需要防止 CSRF,那我们需要在请求中的 headers 加上 X-XSRF-TOKEN,所以变成这样:

axios.post('/user/create', { name: 'beyondxgb' }, {headers: {'X-XSRF-TOKEN': 'xxxxxxxx',},
}).then((result) => {// do something
});

这时可以发现,难道每次发起 post 请求都需要这样配置吗?所以会想到把这部分配置抽离出来,抽象出类似这样一个方法:

function post(url, data, config) {return axios.post(url, data, {headers: {'X-XSRF-TOKEN': 'xxxxxxxx',},...config,});
}

所以我们需要对参数配置进行抽象。

到了测试流程的时候,发现服务端的请求不是总返回成功的,那怎么办?那就 catch 处理一下:

post('/user/create', { name: 'beyondxgb' }).then((result) => {// do something
}).catch((error) => {// deal with error// 200// 503// SESSION EXPIRED// ...
});

写下来总感觉哪里不对啊,原来请求错误有这么多情况,我整个项目有很多请求数据的地方呢,这部分代码肯定是通用的,抽象出来!

function dealWithRequestError(error) {// deal with error// 200// 503// SESSION EXPIRED// ...
}
function post(url, data, config) {return axios.post(url, data, {headers: {'X-XSRF-TOKEN': 'xxxxxxxx',},...config,}).catch(dealWithRequestError);
}

所以我们需要对异常处理进行抽象。

项目上线前业务方可能提出稳定性的需求,这时我们需要对请求进行监控,把接口请求成功和失败的情况都记录下来。同样,我们把这部分代码也要写到公用的地方,类似这样:

function post(url, data, config) {return axios.post(url, data, {headers: {'X-XSRF-TOKEN': 'xxxxxxxx',},...config,}).then((result) => {// 记录成功情况...return result;}).catch((error) => {// 记录失败情况...return dealWithRequestError(error););
}

所以我们需要对请求监控进行抽象。

方案设计

从上面对一个简单的 post 请求的案例分析中,我们可以看到,数据请求主要涉及三方面 参数配置异常处理请求监控。上面例子的处理还是比较粗糙,整体上还是需要进行代码组织和分层。

参数配置

首先,我们处理下参数的配置,上面的例子只是对 post 请求作了分析,其实对于其他比如 getput 都一样的,我们可以对这些请求作统一的处理。

request.js

import axios from 'axios';// The http header that carries the xsrf token value { X-XSRF-TOKEN: '' }
const csrfConfig = {'X-XSRF-TOKEN': '',
};
// Build uniform request
async function buildRequest(method, url, params, options) {let param = {};let config = {};if (method === 'get') {param = { params, ...options };} else {param = JSON.stringify(params);config = {headers: {...csrfConfig,},};config = Object.assign({}, config, options);}return axios[method](url, param, config);
}export const get = (url, params = {}, options) => buildRequest('get', url, params, options);
export const post = (url, params = {}, options) => buildRequest('post', url, params, options);

这样的话,我们对外就暴露出 getpost 的方法,其他请求类似,在此只用 getpost 作为示例,入参分别是 API地址数据扩展配置

异常处理

其实异常处理场景会比较复杂,不是简单地 catch 一下,往往伴随着业务逻辑UI的交互,异常主要有两方面,全局异常业务异常

全局异常,也可以说是通用的异常,比如服务端返回503,网络异常,登录失效,无权限等,这些异常是可以预料并可控的,只要和服务端约定好格式,捕获下异常再展示出来即可。

业务异常,指的是和业务逻辑紧密相关的,比如提交失败,数据校验失败等,这些异常往往每个接口有不一样的情况,而且需要个性化展示错误,所以这部分可能不能进行统一处理,有时候需要把展示错误交到 View 层去实现。

在实现上,我们不会直接在上面的请求方法中直接 catch,而是利用 axios 提供的 interceptors 功能,这样可以将异常的处理和核心的请求方法隔离出来,毕竟这部分是要和 UI 进行交互的。我们来看看如何实现:

error.js

import axios from 'axios';// Add a response interceptor
axios.interceptors.response.use((response) => {const { config, data } = response;// 和服务端约定的 Codeconst { code } = data;switch (code) {case 200:return data;case 401:// 登录失效break;case 403:// 无权限break;default:break;}if (config.showError) {// 接口配置指定需要个性化展示错误return Promise.reject(data);}// 默认展示错误// ... Toast error
}, (error) => {// 通用错误if (axios.isCancel(error)) {// Request cancel} else if (navigator && !navigator.onLine) {// Network is disconnect} else {// Other error}return Promise.reject(error);
});

axiosinterceptors 功能,其实就是一个链式调用,可以在请求前和请求后做事情,这里我们在请求后进行拦截处理,对返回的数据进行校验和捕获异常,对于通用的错误我们直接通过 UI 交互将错误展示出来,对于业务上的错误我们检查下接口有没有配置说要个性化展示错误,如果有的话,将错误处理交给页面,如果没有的话,进行错误兜底处理。

请求监控

请求监控这块和异常处理类似,只不过这里只是记录情况,不涉及到 UI 上的交互或者和业务代码的交互,所以可以把这部分逻辑直接写在异常处理那里,或者在请求后再添加一个拦截器,单独处理。

monitor.js

axios.interceptors.response.use((response) => {const { status, data, config } = response;// 根据返回的数据和接口参数配置,对请求进行埋点
}, (error) => {// 根据返回的数据和接口参数配置,对请求进行埋点
});

比较建议这样做,保持每个模块独立,符合单一功能原则(SRP)。

好了,到现在为止,参数配置异常处理请求监控 都设计完了,有三个文件:

  • request.js:请求库配置,对外暴露出 getpost 方法。
  • error.js:请求的一些异常处理,涉及到和外面对接的是该接口是否需要个性化展示错误。
  • monitor.js:请求的情况记录,比较独立的一块。

那在页面上调用的时候可以这样子:

import { get, post } from 'request.js';get('/user/info').then((data) => {});
post('/user/update', { name: 'beyondxgb' }, { showError: true }).then((data) => {if (data.code !== 200) {// 展示错误} else {// do something}
});

再仔细思考下,觉得还不是最完美的,API 名称直接在页面上引用,这样会给自己埋坑,如果后面 API 名称改了,而且这个 API 在多个页面被调用,那维护成本就高了。我们有两种方法,第一种就是将所有 API 独立配置在一个文件中,给页面去读取,第二种办法就是我们在请求库和页面之前再加一层,叫 service,也就是所谓的服务层,对外暴露接口方法给页面,这样页面完全不需要关注接口是什么或者接口是如何取数据的,而且以后接口的任何修改,只要在服务层进行修改即可,对页面没有任何影响。

当然我是采取第二种方法,类似这样子:

services.js

import { get, post } from 'request.js';// fetch random data
export async function fetchRandomData(params) {return get('https://randomuser.me/api', params);
}// update user info
export async function updateUserInfo(params, options) {return post('/user/info', params, { showError: true, ...options });
}

这样子的话,页面就不会直接和请求库进行交互,而是跟服务层获取对应的方法。

import { fetchRandomData, updateUserInfo } from 'services.js';fetchRandomData().then((data) => {});
updateUserInfo({ name: 'beyondxgb' }).then((data) => {if (data.code !== 200) {// 展示错误} else {// do something}
});

我们来看看最终的方案是这样子的:

延伸扩展

上面讲的都是以 axios 这个请求库为例,其实思想是互通的,换一个请求库也是一样的处理的方法。不知大家有没有注意到,把请求库参数配置和异常处理两个模块独立出来,完全是利用了 interceptors 的特性,这也是我喜欢 axios 的原因之一,我觉得这个设计得很好,类似中间件的做法,在请求数据到达页面之前,我们可以通过写拦截器对数据进行过滤加工校验异常监控等。

我觉得任何一个请求库都可以实现这个功能,就算请求库是有历史包袱,也可以自己在外面包一层。比如说有请求库 abc,它有一个 request 方法,可以这样复写它:

import abc from 'abc';function dispatchRequest(options) {const reqConfig = Object.assign({}, options);return abc.request(reqConfig).then(response => ({response,options,})).catch(error => (Promise.reject({error,options,})));
}class Request {constructor(config) {this.default = config;this.interceptors = {request: new InterceptorManager(),response: new InterceptorManager(),};}
}Request.prototype.request = function request(config = {}) {// Add interceptorsconst chain = [dispatchRequest, undefined];let promise = Promise.resolve(options);// Add request interceptorsthis.interceptors.request.forEach((interceptor) => {chain.unshift(interceptor.fulfilled, interceptor.rejected);});// Add response interceptorsthis.interceptors.response.forEach((interceptor) => {chain.push(interceptor.fulfilled, interceptor.rejected);});while (chain.length) {promise = promise.then(chain.shift(), chain.shift());}return promise;
};

更多

前面我们很好地解决了数据请求的问题,还有另一方面,也是和数据请求紧密相关的,就是数据模拟(Mock) 了,在项目开发前期服务端没有准备好数据之前,我们只有自己在本地进行 Mock 数据了,或者很多公司已经有比较好的平台实现这个功能了,我这里介绍下不借助平台,只是在本地启动一个小工具即可实现 Mock 数据。

这里我自己写了一个小工具 @ris/mock,只要把它作为中间件注入到 webpack-dev-server 中就好了。

webpack.config.js

const mock = require('@ris/mock');module.exports = {//...devServer: {compress: true,port: 9000,after: (app) => {// Start mock datamock(app);},}
};

这时候在项目根目录建立 mock 文件夹,文件夹里建一个 rules.js 文件,rules.js 里面配置的是接口的映射规则,类似这样子:

module.exports = {'GET /api/user': { name: 'beyondxgb' },'POST /api/form/create': { success: true },'GET /api/cases/list': (req, res) => { res.end(JSON.stringify([{ id: 1, name: 'demo' }])); },'GET /api/user/list': 'user/list.json','GET /api/user/create': 'user/create.js',
};

配置规则后,请求接口的时候,就会被转发,转发的时候可以是一个 对象函数文件,详细使用可以参考文档。

结语

在数据请求方案的设计中,也证实了我们的“写代码”是“程序设计”,而不是“程序编写”,我们要对自己的代码负责,如何让自己的代码可维护性高,易扩展,是优秀工程师的基本素养。

以上的方案已沉淀在 RIS 中,包含代码组织结构和技术实现,可以初始化一个 Standard 应用看看,之前的文章《RIS,创建 React 应用的新选择》 有简单提过,欢迎大家体验。

options请求_前端数据请求的终极方案相关推荐

  1. layui 传递前端请求_前端请求后端,后端查询完毕传到前端 ,用layui 将 数据分页...

    前端:我用的是layui框架的分页 js 文件 layui.config({ base : "script/" }).use(['form','layer','jquery','l ...

  2. promise的状态以及api介绍_前端 api 请求缓存

    作者:wsafight https://github.com/wsafight/personBlog/issues/2 web开发时,性能都是必不可少的话题.对于webpack打包的单页面应用程序而言 ...

  3. ajax 请求_前端后分离Ajax跨域请求保证Session一致

    JAVA码牛牛,程序员学习指南 前后端分离的项目,使用Ajax请求一般都出现跨域的问题. 跨域的时候所创建的session是不会被浏览器保存下来的.所以每次进行跨域请求时,服务器都认为不是同一个浏览器 ...

  4. 发起http请求_关于HTTP请求发起和响应你了解多少

    在一个web程序开发中,一般都有前端和后端之分,前端负责向后端请求数据和展示页面,后端负责接收请求和做出响应发回给前端,他们之间的协作桥梁是API,而API其实就是一个URL,作为HTTP连接的一种具 ...

  5. 前端 重构时需要注意的事项_前端数据层落地实践

    源宝导读:天际移动平台经过重构改版,近期正式发布了1.0版本,我们在低代码开发方面做了进一步增强.本文主要围绕前端Model.前端业务逻辑(领域模型).数据层与视图层解耦(包装器模式)3个方面,给大家 ...

  6. 前端md转html添加样式_前端文档站点搭建方案

    ? 这是第 46篇不掺水的原创,想要了解更多,请戳上方蓝色字体:政采云前端团队关注我们吧- 本文首发于政采云前端团队博客:前端文档站点搭建方案 https://www.zoo.team/article ...

  7. 大数据建设意义_大数据技术平台建设方案(ppt)

    应急指挥一张图可视化平台方案(ppt) 大数据平台架构建设方案(图文) 大数据平台技术架构解决方案(ppt)大数据平台总体架构方案(ppt)大数据平台框架选型分析方案(图文)大数据可视化分析平台应用方 ...

  8. ajax put请求_前端基础面试:手写一个ajax,说说XMLHttpRequest 都有哪些属性?

    前言 在 Ajax 出现之前,网页想要和服务器通信,最常用的方式是使用 form 表单:用户提交表单后,浏览器就开始跳转,服务器接收表单并处理,然后将新的网页返回给浏览器:整个过程用户都只有等待,用户 ...

  9. c++ post请求_前端工程师进阶:网络请求方法详解,GET和POST的区别

    1 前言 最近看了一些同学的面经,发现无论什么技术岗位,还是会问到 get 和 post 的区别,而搜索出来的答案并不能让我们装得一手好逼,那就让我们从 HTTP 报文的角度来撸一波,从而搞明白他们的 ...

最新文章

  1. shell if else
  2. 被小扎誉为整个科技界的愿景,元宇宙到底是什么?
  3. android序列化异常,关于序列化:错误:Android中的序列化和反序列化
  4. 开发者在对项目失去信心后,该做什么?
  5. 做网上商城项目的一点记录
  6. 【Flink】Flink SQL 开源UI平台 flink-streaming-platform-web
  7. python提取excel数据视频_Python-爬取b站的热门视频并导入Excel中
  8. 获取完整拼音中包含拼音的个数
  9. 电厂供配电模拟实训系统QY-GPD03
  10. java利用poi导出word文档
  11. The setting mapUnderScoreToCamelCase is not known. Make sure you spelled it correctly (case sensiti
  12. 笔记本启动显示0xc000014c错误--提示缺失win10/system32内部文件
  13. 2021年江西省研究生数学建模竞赛题目(一)题目:某肿瘤疾病诊疗的经济学分析
  14. Loss——Focal Loss
  15. 前端集成解决方案(webpack、gulp)
  16. 各大电商API详细数据获取
  17. iSCSI存储技术全攻略【存储部落】云存储|云计算|云服务
  18. 丘成桐大学生数学竞赛2014年分析与方程个人赛试题第一题另解
  19. php检测gd2_PHP开启GD库及检测
  20. Ruby中Time的常用函数

热门文章

  1. 带宽与流量的基本概念(更新)
  2. Android O 迁移应用官方指南
  3. 如何看Cortex-M系列处理器差异与共性?技术老司机Joseph带你飞
  4. leetcode 983. Minimum Cost For Tickets | 983. 最低票价(动态规划)
  5. 【Java多线程】创建多线程的三种方式
  6. C#连接MySQL数据库 制作股票交易模拟程序
  7. java高级用法之:JNA类型映射应该注意的问题
  8. Spring5参考指南:组件扫描
  9. 前后端分离的思考与实践
  10. dict python用法_Python_Dict用法梳理