如何实现 axios 的自定义适配器 adapter
Axios 是一个非常优秀的基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。并且提供了很多便捷的功能,例如:
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
但如果我们想基于 axios 扩展一些自己的数据请求方式(例如 mock 数据,某些 APP 内专属的数据请求方式等),并能够使用上 axios 提供的便捷功能,该怎么自定义一个适配器 adapter;
1. 适配器要实现的功能
我们在基于 axios 实现额外的数据模块时,应当与 axios 的模式进行对齐。因此在返回的数据格式上,实现的功能上尽量保持一致。
1.1 promise 和工具
所有的适配均应当实现为 Promise 方式。
而且,有些功能的实现,axios 将其下放到了适配器中自己进行实现,例如
- url 的拼接:即 baseURL 和 url 的拼接,若存在 baseURL 且 url 为相对路径,则进行拼接,否则直接使用 url;
- 参数的拼接:若是 get 请求,需要自行将 object 类型拼接为 url 参数的格式并与 url 拼接完成;
这是自己需要实现的两个基本的工具方法。
1.2 响应的格式
这里我们要注意到请求接口正常和异常的格式。
接口正常时:
const result = {
status: 200, // 接口的http 状态
statusText: 'ok',
config: 'config', // 传入的config配置,原样返回即可,方便在响应拦截器和响应结果中使用
data: {}, // 真实的接口返回结果
};
接口异常时,我们可以看下 axios 源码中对错误信息的处理createError,enhanceError(createError 中调用了 enhanceError),首先会创建一个 error 实例,然后给这个 error 实例添加一个属性:
module.exports = function enhanceError(error, config, code, request, response) {
error.config = config;
if (code) {
error.code = code;
}
error.request = request;
error.response = response;
error.isAxiosError = true;
error.toJSON = function toJSON() {
return {
// Standard
message: this.message,
name: this.name,
// Microsoft
description: this.description,
number: this.number,
// Mozilla
fileName: this.fileName,
lineNumber: this.lineNumber,
columnNumber: this.columnNumber,
stack: this.stack,
// Axios
config: this.config,
code: this.code,
};
};
return error;
};
可以看到,除了正常的错误信息外,还加入了很多别的属性,例如 request, response, config 等。这里我们在自己实现适配器时,最好也要这样统一编写,方便更上层的业务层统一处理,避免为单独的适配器进行特殊处理。
关于 1.1 和 1.2 中的内容,若不进行打包编译,则需要自己实现。若还要通过 webpack 等打包工具编译一下的,可以直接引用 axios 中的方法,不用自己实现了,参考官方基于 axios 实现的mock-axios。例如:
import axios from 'axios';
import buildURL from 'axios/lib/helpers/buildURL';
import isURLSameOrigin from 'axios/lib/helpers/isURLSameOrigin';
import btoa from 'axios/lib/helpers/btoa';
import cookies from 'axios/lib/helpers/cookies';
import settle from 'axios/lib/core/settle';
import createError from 'axios/lib/core/createError';
然后直接使用就行了,不用再进行二次开发。
1.3 超时设置
我们不能无限地等待第三方服务的响应,如果第三方服务无响应或者响应时间过长,应当适时的终止掉。在 axios 中,前端使用了XMLHttpRequest
,在 node 端使用了http
,来实现接口的请求,两者都有超时的设定,可以设置 timeout 字段来设置超时的时间,自动取消当前的请求。
像有的发起的请求,自己并没有超时的设定,例如 jsonp,是用创建一个 script 标签来发起的请求,这个请求必须等到服务器有响应才会终止(成功或者失败)。这时,就需要我们自己用一个setTimeout
来模拟了,但这样,即使返回给业务层说“超时了,已取消当前请求”,但实际上请求还在,只不过若超过规定时间,只是不再执行对应的成功操作而已。
1.4 主动取消请求
我们也会有很多并没有到超时时间,就需要主动取消当前请求的场景,例如在请求返回之前就切换了路由;上次请求还没响应前,又需要发出新的请求等。都需要主动地取消当前请求。
axios 中已经提供了取消请求的功能,我们只需要按照规则接入即可。我们来看下 XMLHttpRequest 请求器中是怎么取消请求的,在写自定义请求器时也可以照理使用。
// 若config中已经配置了cancelToken
if (config.cancelToken) {
// Handle cancellation
// 若在外城执行了取消请求的方法,则这里将当前的请求取消掉
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
// xhr中使用abort方法取消当前请求
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
我们在写自己的适配器时,也可以将这段拷贝过去,将内部取消的操作更换为自己的即可。
关于 cancel 操作执行的原理,这里暂不展开,有兴趣的可以参考这篇文章:axios cancelToken 原理解析。
到这里,若把上面的功能都实现了,就已经完成了一个标准的适配器了。
2. 编写自定义适配器
每个人需要的适配器肯定也不一样,复杂度也不一样,例如有的想接入小程序的请求,我自己想接入客户端里提供的数据请求方式等。我们这里只是通过实现一个简单的jsonp适配器
来讲解下实现方式。
我们以 es6 的模块方式来进行开发。所有的实现均在代码中进行了讲解。
// 这里的config是axios里所有的配置
const jsonpAdapter = (config) => {
return new Promise((resolve, reject) => {
// 是否已取消当前操作
// 因jsonp没有主动取消请求的方式
// 这里使用 isAbort 来标识
let isAbort = false;
// 定时器标识符
let timer = null;
// 执行方法的名字,
const callbackName = `jsonp${Date.now()}_${Math.random()
.toString()
.slice(2)}`;
// 这里假设已经实现了baseURL和url的拼接方法
const fullPath = buildFullPath(config.baseURL, config.url);
// 这里假设已经实现了url和参数的拼接方法
// 不太一样的地方在于,jsonp需要额外插入一个自己的回调方法
const url = buildURL(
fullPath,
{
...config.params,
...{ [config.jsonpCallback || 'callback']: callbackName },
},
config.paramsSerializer
);
// 创建一个script标签
let script = document.createElement('script');
// 成功执行操作后
function remove() {
if (script) {
script.onload = script.onerror = null;
// 移除script标签
if (script.parentNode) {
script.parentNode.removeChild(script);
}
// 取消定时器
if (timer) {
clearTimeout(timer);
}
script = null;
}
}
// 成功请求后
window[callbackName] = (data) => {
// 若已需要请求,则不再执行
if (isAbort) {
return;
}
// 返回的格式
const response = {
status: 200,
statusText: 'ok',
config,
request: script,
data: data,
};
remove();
// 实际上这里上一个settle操作,会额外判断是否是合理的status状态
// 若我们在config.validateStatus中设置404是合理的,也会进入到resolve状态
// 但我们这里就不实现这个了
// settle(resolve, reject, response);
resolve(response);
};
// 请求失败
script.onerror = function (error) {
remove();
reject(createError('Network Error', config, 404));
};
// 若设置了超时时间
if (config.timeout) {
timer = setTimeout(function () {
remove();
// 取消当前操作
isAbort = true;
reject(
createError(
'timeout of ' + config.timeout + 'ms exceeded',
config,
405
)
);
}, config.timeout);
}
// 若定义了取消操作
if (config.cancelToken) {
config.cancelToken.promise.then(function () {
if (!script) {
return;
}
remove();
isAbort = true;
reject(createError('Cancel Error', config, 404));
});
}
script.src = url;
const target =
document.getElementsByTagName('script')[0] || document.head;
target.parentNode && target.parentNode.insertBefore(script, target);
});
};
export default jsonpAdapter;
3. 将适配器添加到 axios 中
axios 的 config 提供了 adapter 字段让我们插入自己的适配器。使用自定义适配器又有两种情况:
- 完全只使用自定义的适配器;
- 在某种情况下使用自定义适配器,其他情况时还是使用 axios 自己的适配器。
第 1 种情况还好,只需要 return 自己适配器返回的结果结果即可;而第 2 种情况中,则有个小坑需要踩一下,我们这里也只讲解下第 2 种情况。我要把刚才实现的 jsonp 适配器添加到 axios 中,并且只在参数有format=jsonp
时才调用该适配器,其他还是用的 axios 提供的适配器。
import Axios from 'axios';
import jsonpAdapter from './jsonpAdater';
const request = Axios.create({
adapter: (config) => {
if (config?.params?.format === 'jsonp') {
return jsonpAdapter(config);
}
// 这里需要将config.adapter设置为空
// 否则会造成无限循环
return defaultAxios({ ...config, ...{ adapter: undefined } });
},
});
使用方式,点击查看 demo【axios 自定义的 jsonp 适配器】:
使用自定义的适配器 jsonp 发起请求。
// 使用自定义的适配器jsonp发起请求
var options = {
params: {
format: 'jsonp',
},
};
request(
'https://api.prize.qq.com/v1/newsapp/answer/share/oneQ?qID=506336',
options
)
.then(function (response) {
console.log('jsonp response', response);
})
.catch(function (error) {
console.error('jsonp error', error);
});
使用 axios 默认的适配器发起请求。
// 使用axios默认的适配器发起请求
request('https://api.prize.qq.com/v1/newsapp/answer/share/oneQ?qID=506336')
.then(function (response) {
console.log('axios response', response);
})
.catch(function (error) {
console.error('axios error', error);
});
4. 总结
这里,我们就已经实现了一个自定义适配器了,在满足一定条件时可以触发这个适配器。通过这个思路,我们也可以实现一个自定义的 mock 方法,例如当参数中包含format=mock
时则调用 mock 接口,否则就正常请求。
也欢迎关注我的公众号,一起学习讨论。
如何实现 axios 的自定义适配器 adapter的更多相关文章
- 最简单的自定义适配器adapter
下面是一个非常简单的自定义适配器的总体源码,从这个源码入门,就可以慢慢学会适配器了 适配器的作用: 完成数据和界面控件的绑定,把数据绑定到界面的现实控件条目上(对于listView,应该是listVi ...
- 自定义ListView适配器Adapter引用布局文件的情况下实现点击列表项时背景颜色为灰色
listview控件设置适配器的时候,如果使用自定义的adapter,比如MyArrayAdapter extends ArrayAdapter<String> 如果listitem布局文 ...
- 细解ListView之自定义适配器
下面我们将以一个例子来讲述ListView之自定义适配器 首先我们看一下效果图: [分析] 首先:需要创建一个ListView控件,自定义适配器是为了实现自定义ListView的ListView_It ...
- Android -- ListView(SimpleAdapter) 自定义适配器
aaarticlea/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA ...
- ListView使用自定义适配器的情况下实现适配器的文本和图标控件点击事件执行Activity界面中的方法
ListView使用的是自定义适配器,列表项的布局文件中含有文本和图标,实现文本区域和图标区域的点击事件. 实现思路:在自定义适配器MyArrayAdapter 类型中自定义接口和接口方法,分别设置文 ...
- ListView使用自定义适配器的情况下实现适配器的控件点击事件执行Activity界面中的方法
如果ListView使用的是自定义的适配器,比如MyArrayAdapter extends ArrayAdapter<String> 那么,如何实现适配器中的点击事件执行activity ...
- Android:自定义适配器
无论是ArrayAdapter还是SimpleAdapter都继承了BaseAdapter,自定义适配器同样继承BaseAdapter 实例:Gallery实现图片浏览器 <?xml versi ...
- BaseAdapter自定义适配器——思路详解
BaseAdapter自定义适配器——思路详解 引言: Adapter用来把数据绑定到扩展了AdapterView类的视图组.系统自带了几个原生的Adapter. 由于原生的Adapter视图功能太少 ...
- XamarinAndroid组件教程RecylerView自定义适配器动画
XamarinAndroid组件教程RecylerView自定义适配器动画 如果RecyclerViewAnimators.Adapters命名空间中没有所需要的适配器动画,开发者可以自定义动画.此时 ...
随机推荐
- Mybatis快速逆向生成代码
先下载生成器的文件, 并在eclipse或者IDEA里面打开这个工程 热乎乎的链接 然后配置一下 选择你需要生成的数据的ip和端口 点击运行入口函数 运行成功 接着在浏览器输入localhost: 这 ...
- vulnhub靶机之Quaoar
Quaoar 靶机非常简单. 扫描端口主机. wordpress建站. 扫到两个用户,分别是wpuser以及admin. 爆破出了后台: 传了一个一句话木马,连上蚁剑又传了个反弹shell. 反弹sh ...
- 阿里云恶意软件检测比赛-第三周-TextCNN
LSTM初试遇到障碍,使用较熟悉的TextCNN. 1.基础知识: Embedding:将词的十进制表示做向量化 起到降维增维的作用 嵌入维度数量(New Embedding维度)的一般经验法则: e ...
- redis过期策略以及内存淘汰机制(理论+配置)
一.redis的过期策略: redis的过期策略是:定期删除+惰性删除redis在存储数据时,可能会设置过期时间,而所谓的定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的k ...
- Python-如何在一个for循环中迭代多个可迭代对象?
案例: 某班学生期末考试成绩,语文.数学.英语分别存储在3个列表中,同时迭代三个列表.,计算每个学生的总分(并行) 某年级有4个班,某次英语成绩分别记录在4个列表中,依次迭代每个列表,统计全年级高于9 ...
- mysql-16-variables
#变量 /* 系统变量: 全局变量 会话变量 自定义变量: 用户变量 局部变量 */ # 一.系统变量 #由系统提供,属于服务器层面 #1.查看所有的系统变量 show global variable ...
- Android Handler MessageQueue Looper 消息机制原理
提到Android里的消息机制,便会提到Message.Handler.Looper.MessageQueue这四个类,我先简单介绍以下这4个类 之间的爱恨情仇. Message 消息的封装类,里边存 ...
- 0923 lca练习
P1967 货车运输 题目描述 A 国有 nnn 座城市,编号从 11 1 到 n nn,城市之间有 mmm 条双向道路.每一条道路对车辆都有重量限制,简称限重. 现在有 qqq 辆货车在运输货物, ...
- spring-boot-route(四)全局异常处理
在开发中,我们经常会使用try/catch块来捕获异常进行处理,如果有些代码中忘记捕获异常或者不可见的一些异常出现,就会响应给前端一些不友好的提示,这时候我们可以使用全局异常处理.这样就不用在代码中写 ...
- 温故知新——C++--封装
参考: 1.https://blog.csdn.net/cherrydreamsover/article/details/81942293 2.https://www.cnblogs.com/ji ...