最近参照antd-pro脚手架进行开发,因此接触到了umi-request。

umijs/umi-request​github.com

umi-request对fetch进行了封装,简化了api的使用,结合了fetch和axios的特点,具体可参照umi-request的readme介绍。

文件结构

核心文件夹为src文件夹,内含

  • lib文件夹为修改后的fetch.js;
  • defaultInterceptor.js用于注册拦截器;
  • index.js是外部调用的入口文件;
  • request.js提供request实例;
  • utils.js定义了处理缓存、请求异常、响应异常、处理gbk、json语法分析的功能类;
  • wrapped-fetch.js从文件名可以看出来为封装fetch,实现更新版数据的传递;
  • wrapped-rpc.js从文件可以看出封装了rpc通信,待实现;

代码分析

原始代码里含的注释基本上已经很全面了

defaultInterceptor.js

功能有:数据提交方式简化针对常见两种数据传输格式补全文件头“Accept: 'application/json', 'Content-Type': 'application/json(或者x-www-form-urlencoded);charset=UTF-8'”/url 参数自动序列化

主要是对传入的url和option进行格式化的处理,以便后续的fetch.js进行处理。
关于option的新增参数参见request.js。

export default (url, originOptions = {}) => {const options = { ...originOptions };// 默认get, 兼容method大小写let method = options.method || 'get';method = method.toLowerCase();if (method === 'post' || method === 'put' || method === 'patch' || method === 'delete') {// requestType 简写默认值为 jsonconst { requestType = 'json', data } = options;// 数据使用类axios的新字段data, 避免引用后影响旧代码, 如将body stringify多次if (data) {const dataType = Object.prototype.toString.call(data);if (dataType === '[object Object]' || dataType === '[object Array]') {if (requestType === 'json') {options.headers = {Accept: 'application/json','Content-Type': 'application/json;charset=UTF-8',...options.headers,};options.body = JSON.stringify(data);} else if (requestType === 'form') {options.headers = {Accept: 'application/json','Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',...options.headers,};options.body = stringify(data);}} else {// 其他 requestType 自定义headeroptions.headers = {Accept: 'application/json',...options.headers,};options.body = data;}}}// 支持类似axios 参数自动拼装, 其他method也可用, 不冲突.if (options.params && Object.keys(options.params).length > 0) {const str = url.indexOf('?') !== -1 ? '&' : '?';url = `${url}${str}${stringify(options.params)}`;}return {url,options,};
};

fetch.js

主要是定义了request和response拦截器

拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截,然后在之前或之后加入某些操作。可以对http请求进行批量处理。

import 'whatwg-fetch';
import defaultInterceptor from '../defaultInterceptor';const requestInterceptors = [];
export const responseInterceptors = [];function fetch(url, options = {}) {if (typeof url !== 'string') throw new Error('url MUST be a string');// 执行 request 的拦截器,使用defaultInterceptor,依据handler方式对url,option进行处理requestInterceptors.concat([defaultInterceptor]).forEach(handler => {const ret = handler(url, options);url = ret.url || url;options = ret.options || options;});// 将 method 改为大写options.method = options.method ? options.method.toUpperCase() : 'GET';// 请求数据let response = window.fetch(url, options);// 执行 response 的拦截器,依据handler方式对url,option进行处理responseInterceptors.forEach(handler => {response = response.then(res => handler(res, options));});return response;
}
// 支持拦截器,参考 axios 库的写法: https://github.com/axios/axios#interceptors
fetch.interceptors = {request: {use: handler => {requestInterceptors.push(handler);},},response: {use: handler => {responseInterceptors.push(handler);},},
};export default fetch;

utils.js

定义了处理缓存(Mapcache)请求异常(RequestError)响应异常(ResponseError)处理gbk(readerGBK)json语法转换(safeJsonParse)的功能类;

  • 处理缓存

options.maxCache是option的extend参数,可以在封装umi-request时指定,

如antd-pro. const request = extend({maxCache: 50,});

实现变量的获取、变量删除、缓存清空、value存入缓存(其中key json化后为关键字)

export class MapCache {constructor(options) {this.cache = new Map();this.timer = {};this.maxCache = options.maxCache || 0;}get(key) {return this.cache.get(JSON.stringify(key));}set(key, value, ttl = 60000) {// 如果超过最大缓存数, 删除头部的第一个缓存.if (this.maxCache > 0 && this.cache.size >= this.maxCache) {const deleteKey = [...this.cache.keys()][0];this.cache.delete(deleteKey);if (this.timer[deleteKey]) {clearTimeout(this.timer[deleteKey]);}}const cacheKey = JSON.stringify(key);this.cache.set(cacheKey, value);if (ttl > 0) {this.timer[cacheKey] = setTimeout(() => {this.cache.delete(cacheKey);delete this.timer[cacheKey];}, ttl);}}delete(key) {const cacheKey = JSON.stringify(key);delete this.timer[cacheKey];return this.cache.delete(cacheKey);}clear() {this.timer = {};return this.cache.clear();}
}

在wrapped-fetch.js中调用

_wrappedCache(instance, useCache) {if (useCache) {const { params, ttl } = this.options;//url请求参数params,和缓存时长ttl为option传入参数return instance.then(response => {// 只缓存状态码为 200的数据if (response.status === 200) {const copy = response.clone();copy.useCache = true;//this.cache为通过request.js调用传入的参数const mapCache = new MapCache(initOptions);this.cache.set({ url: this.url, params }, copy, ttl);}return response;});} else {return instance;}
}

  • 请求异常
export class RequestError extends Error {constructor(text) {super(text);this.name = 'RequestError';}
}

在wrapped-fetch.js中如此使用,用于输出超时信息,其中timeout为option传入参数。需要注意的是超时后客户端虽然返回超时, 但api请求不会断开, 写操作慎用。

return Promise.race([new Promise((_, reject) =>setTimeout(() => reject(new RequestError(`timeout of ${timeout}ms exceeded`)), timeout)),instance,
]);

  • 响应异常
export class ResponseError extends Error {constructor(response, text, data) {super(text || response.statusText);this.name = 'ResponseError';this.data = data;this.response = response;}
}

在wrapped-fetch.js中调用,示例,当异常时抛出异常

catch (e) {throw new ResponseError(response, e.message);}

  • 支持gbk并进行json格式的转换

判断返回的数据时表示gbk编码的,如果是,将数据以gbk格式读入,然后再利用safeJsonParse转化为json格式

export function readerGBK(file) {return new Promise((resolve, reject) => {const reader = new FileReader();reader.onload = () => {resolve(reader.result);};reader.onerror = reject;reader.readAsText(file, 'GBK'); // setup GBK decoding});
}export function safeJsonParse(data) {try {return JSON.parse(data);} catch (e) {} // eslint-disable-linereturn data;
}

wrappedfetch.js

核心部分,基于fetch进行数据交互

一个 Promise 就是一个对象,它代表了一个异步操作的最终完成或者失败,通过 .then() 形式添加的回调函数,连续执行两个或者多个异步操作(链式调用,多个then,一个catch接收异常)。

一个 Promise有以下几种状态:

  • pending: 初始状态,既不是成功,也不是失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法。

Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。但如果这个值是个thenable(即带有then方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态(指resolved/rejected/pending/settled);如果传入的value本身就是promise对象,则该对象作为Promise.resolve方法的返回值返回;否则以该值为成功状态返回promise对象。

Promise.reject(reason)返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法

export default class WrappedFetch {constructor(url, options, cache) {this.cache = cache;//cache是MapCache的实例this.url = url;this.options = options;this._addfix();return this._doFetch();}_doFetch() {//如果使用cache,只有在option的method是get且useCache为true时使用cacheif (useCache) {let response = this.cache.get({url: this.url,params: this.options.params,});if (response) {response = response.clone();let instance = Promise.resolve(response);responseInterceptors.forEach(handler => {instance = instance.then(res => handler(res, this.options));});return this._parseResponse(instance, true);}}let instance = fetch(this.url, this.options);// 处理超时instance = this._wrappedTimeout(instance);// 处理缓存 1.只有get 2.同时参数cache为true 才缓存instance = this._wrappedCache(instance, useCache);// 返回解析好的数据return this._parseResponse(instance);}//对url进行自动序列化,添加前缀和后缀_addfix() {}  //调用RequestError,在超时后输出信息_wrappedTimeout(instance) {}//调用MapCache,在用户使用usecache时将response复制到擦车中,并设置缓存时间_wrappedCache(instance, useCache) {}//处理返回类型, 并解析数据,如果编码方式时gbk则利用readerGBK和safeJsonParse进行转化,在读取response时,加入responseError处理_parseResponse(instance, useCache = false) {}//处理错误_handleError({ reject, resolve }, error) {}
}

request.js

option里支持的参数有:(较fetch增加的)

* @param {string} requestType post类型, 用来简化写content-Type, 默认json
* @param {*} data post数据
* @param {object} params query参数
* @param {string} responseType 服务端返回的数据类型, 用来解析数据, 默认json
* @param {boolean} useCache 是否使用缓存,只有get时有效, 默认关闭, 启用后如果命中缓存, response中有useCache=true. 另: 内存缓存, 刷新就没。一个简单的Map cache, 提供session local map三种前端cache方式.
* @param {number} ttl 缓存生命周期, 默认60秒, 单位毫秒
* @param {number} timeout 超时时长, 默认未设, 单位毫秒
* @param {boolean} getResponse 是否获取response源
* @param {function} errorHandler 错误处理
* @param {string} prefix 前缀
* @param {string} suffix 后缀
* @param {string} charset 字符集, 默认utf8

extend里支持的参数有:

* @param {number} maxCache 最大缓存数
* @param {string} prefix url前缀
* @param {function} errorHandler 统一错误处理方法
* @param {object} headers 统一的headers

method包括:'get', 'post', 'delete', 'put', 'patch','rpc'。其中,'get', 'post', 'delete', 'put', 'patch'属于REST风格,是http协议的一种直接应用,默认基于json作为传输格式,使用简单,学习成本低效率高。RPC是指远程过程调用直观说法就是A通过网络调用B的过程方法,是分布式系统中常见的方法。

  • GET操作是安全且等幂的。所谓安全是指不管进行多少次操作,资源的状态都不会改变。
  • PUT,DELETE操作是幂等的。所谓幂等是指不管进行多少次操作,结果都一样。比如我用PUT修改一篇文章,然后在做同样的操作,每次操作后的结果并没有不同,DELETE也是一样。
  • POST操作既不是安全的,也不是幂等的,比如常见的POST重复加载问题:当我们多次发出同样的POST请求后,其结果是创建出了若干的资源。

安全和幂等的意义在于:当操作没有达到预期的目标时,我们可以不停的重试,而不会对资源产生副作用。从这个意义上说,POST操作需要谨慎。还有一点需要注意的就是,创建操作可以使用POST,也可以使用PUT,区别在于POST 是作用在一个集合资源之上的(/uri),而PUT操作是作用在一个具体资源之上的(/uri/xxx),再通俗点说,如果URL可以在客户端确定,那么就使用PUT,如果是在服务端确定,那么就使用POST,比如说很多资源使用数据库自增主键作为标识信息,而创建的资源的标识信息到底是什么只能由服务端提供,这个时候就必须使用POST。

  • PATCH新引入的,是对PUT方法的补充,用来对已知资源进行局部更新。在没有patch之前,我们都是用put进行更新操作,这时候我们的接口中通常会有一个逻辑规则,如:如果对象的的一个字符属性为NULL,那么就是不更新该属性(字段)值,如果对象的字符属性是“”,那么就更新该属性(字段)的值,通过这种方式来避免全部覆盖的操作。现在有了patch就解决了这种判断,在put接口中不管属性是不是null,都进行更新,在patch接口中就对非null的进行更新
import fetch from './lib/fetch';
import { MapCache } from './utils';
import WrappedFetch from './wrapped-fetch';
import WrappedRpc from './wrapped-rpc';/*** 获取request实例 调用参数可以覆盖初始化的参数. 用于一些情况的特殊处理.* @param {*} initOptions 初始化参数*/
const request = (initOptions = {}) => {const mapCache = new MapCache(initOptions);const instance = (input, options = {}) => {options.headers = { ...initOptions.headers, ...options.headers };options.params = { ...initOptions.params, ...options.params };options = { ...initOptions, ...options };const method = options.method || 'get';options.method = method.toLowerCase();if (method === 'rpc') {// call rpcreturn new WrappedRpc(input, options, mapCache);} else {return new WrappedFetch(input, options, mapCache);}};// 增加语法糖如: request.get request.post// 对应着对资源的查看,创建,删除,创建或更新,部分更新,rpcconst methods = ['get', 'post', 'delete', 'put', 'patch','rpc',];methods.forEach(method => {instance[method] = (input, options) => instance(input, { ...options, method });});// 给request 也增加一个interceptors引用;instance.interceptors = fetch.interceptors;return instance;
};/*** extend 方法参考了ky, 让用户可以定制配置.* initOpions 初始化参数* @param {number} maxCache 最大缓存数* @param {string} prefix url前缀* @param {function} errorHandler 统一错误处理方法* @param {object} headers 统一的headers*/
export const extend = initOptions => request(initOptions);
export default request();

使用示例

通过extend进行简单封装,参照antd-pro,dva数据流处理方式。

import { extend } from 'umi-request';
......
const request = extend({errorHandler, // 默认错误处理函数credentials: 'include', // 默认请求是否带上cookie// useCache: false,// maxCache: 50,
});
export default request;

后续在api.js中的使用

import request from '@/utils/request';
//get请求
export async function function1({ classId }) {return request(`/api/classes/${classId}/teachers/`);}
//post请求
export async function function2({ classId }) {return request.post('/api/classes/${classId}/teachers/', { data: {foo: 'bar'}});}
// 使用缓存
export async function function3({ classId }) {return request(`/api/classes/${classId}/teachers/`, { useCache: true, ttl: 10000 });}

每一个页面对应的model中,可以通过下面的方式进行使用

* fetchBasic({ classId }, { call, put }) {//利用call方式调用api.js中声明的function1  const response = yield call(fuction1, classId);//对接收到的response进行处理yield put({type: 'saveResponse',//reducers中对应的处理方法payload: response});
},

特点

  • url 参数自动序列化
  • post 数据提交方式简化
  • response 返回处理简化
  • api 超时支持
  • api 请求缓存支持
  • 支持处理 gbk
  • 类 axios 的 request 和 response 拦截器(interceptors)支持

更多内容查看umi-request项目、dva文档、antd-pro项目

umi-request项目​github.comDva 概念 | DvaJS​dvajs.com

antd-pro项目​github.com

antd request 通过jsessionid传参数_Umi-request源码阅读相关推荐

  1. Soul网关源码阅读番外篇(一) HTTP参数请求错误

    Soul网关源码阅读番外篇(一) HTTP参数请求错误 共同作者:石立 萧 * 简介     在Soul网关2.2.1版本源码阅读中,遇到了HTTP请求加上参数返回404的错误,此篇文章基于此进行探索 ...

  2. KindEditor ASP.NET 上传/浏览服务器 附源码

    KindEditor是一个不错的网页在线编辑器, 早就想把它用在自己的项目中 可是它只提供了asp,hp,jsp上传的类, 没有提供Asp.net的上传和浏览程序. 当时看的是PHP的搞的一头雾水.. ...

  3. 飞鸽传书最新C++源码:这两个消息钩子

    飞鸽传书最新C++源码:这两个消息钩子的 飞鸽传书 最新源码钩子函数极其相似就不分开说明,只说明一个消息钩子子程的流程.2.排除密码框.3.如果不是密码框,则钩子函数中对键盘消息进行记录,特别处理wm ...

  4. 一码空传临时网盘源码-带提取码功能

    介绍: 一码空传临时网盘源码,无数据库版V2.0,免费授权. 前端采用layui开发框架,后端是原生PHP,没有使用任何的开发框架. 采用了一个无数据库配置读写类,config文件读写代码来自网络. ...

  5. PHP一码空传临时网盘源码2.0+带提取码模块

    正文: PHP一码空传临时网盘源码+带提取码模块,前端采用layui开发框架,后端是原生PHP,没有使用任何的开发框架. 采用了一个无数据库配置读写类,config文件读写代码来自网络. 使用提取码提 ...

  6. MySQL核心参数含义的源码解析

    引言 你访问的网站,大部分使用Apache服务器;你访问的网站,大部分使用Linux或BSD操作系统:你访问的网站,大部分使用MySQL数据库;你提交DNS域名查询请求大多由BIND服务器分析处理;你 ...

  7. 【JAVA EE#6】【传智书城·源码阅读】后台管理模块:权限控制+页面分析+商品管理+销售榜单+订单管理+公告管理+项目结构思维导图

    权限控制 普通用户只能访问client文件夹下面的jsp文件,对于没有权限操作的admin文件夹就会提示错误,而超级用户同时可以访问两者,一直很好奇这个权限限制怎么实现的. 原来在存在一个AdminP ...

  8. 文件已经上传到服务器翻译,服务器接受上传的优化 翻译+源码分析

    一般的做法 err := r.ParseMultipartForm(32 << 20) // 32Mb if err != nil { http.Error(w, err.Error(), ...

  9. 五:Dubbo中Provider参数配置及源码讲解

    dubbo.application.register-mode 参数 参数含义 interface 只接口级注册(/dubbo) instance 只应用级注册(/service) all 接口级注册 ...

最新文章

  1. leetcode-24 两两交换链表中的节点
  2. interp3函数-----三维数据插值
  3. Sherman-Morrison公式及其应用
  4. LeetCode 1022. 从根到叶的二进制数之和(递归)
  5. 昇腾AI处理器软件栈--任务调度器(TS)
  6. 60-60-020-API-Kafka Java consumer动态修改topic订阅
  7. SpringMVC系列(十四)Spring MVC的运行流程
  8. svn怎么比对文件_svn导出文件进行比较
  9. JAVA导入gpx文件_使用传单加载多个gpx文件
  10. c语言中isupper用法,isupper - [ C语言中文开发手册 ] - 在线原生手册 - php中文网
  11. 2021大学生笔记本电脑购买指南
  12. 微习惯养成,互联网产品成败的关键因素
  13. App Indexing
  14. MLY -- 9.Optimizing and satisficing metrics
  15. 从0开发游戏引擎之引擎基础组件-Node类实现
  16. 解决打开CHM文件后,右侧显示空白
  17. springboot+nodejs+vue+elementui会议室考勤签到管理系统java
  18. PAT_1027 (Basic Level) Practise (中文)
  19. 漫画 | 程序员上班时戴耳机都在听什么?
  20. idea类注释文件的模板配置

热门文章

  1. asp.net core sdk runtime 镜像[已更新至2.2.0]
  2. [机器翻译]参与 Microsoft 开放源代码软件项目的方式
  3. asp.net core添加全局异常处理及log4net、Nlog应用
  4. 微软中国Azure开源开发者(深圳)研讨会
  5. ASP.NET Core 之 Identity 入门(二)
  6. 使用 Autofac 进行依赖注入
  7. Django08:模型层(ORM)--测试脚本/必知的13条/神器的双下划线查询/多表操作
  8. 目前市场上用于个人计算机的硬盘尺寸是,第5章-硬盘(计算机组装与维护).docx
  9. 【ArcGIS微课1000例】0001:添加XY数据(Add XY data)生成shp
  10. 【Envi风暴】Envi 5.3 SP1经典安装手把手图文教程(含补丁文件)