本文详细解析如何在axios中实现自定义适配器(adapter)。内容涵盖适配器的核心功能要求,包括Promise封装、URL与参数拼接、响应格式标准化、超时处理及请求取消机制。通过一个完整的JSONP适配器示例,演示如何编写适配器并将其注册到axios中,支持条件性调用默认或自定义逻辑,帮助开发者灵活扩展axios的请求能力。
1. 适配器要实现的功能
在基于 axios 开发额外的数据模块时,应遵循 axios 的设计模式,确保返回的数据格式和功能行为尽可能保持一致。
1.1 promise 和工具
所有适配器必须实现为 Promise 模式。
此外,axios 将部分功能下放给适配器自行实现,例如:
- 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 实例,然后为该实例添加属性:
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.js 端使用 http 实现接口请求,两者均支持 timeout 字段设置超时时间,自动取消当前请求。
对于没有内置超时机制的请求,如 JSONP(通过创建 script 标签发起),必须等待服务器响应(成功或失败)才会终止。此时,需自行使用 setTimeout 模拟超时,但即使返回给业务层“超时,已取消”,实际请求仍在进行,只是超过规定时间后不再执行成功操作。
1.4 主动取消请求
许多场景需要在未到达超时时间前主动取消请求,例如在请求返回前切换路由,或在上次请求未响应时发出新请求。这些情况都需要主动取消当前请求。
axios 已提供取消请求功能,只需按规则接入即可。查看 XMLHttpRequest 请求器中如何取消请求,编写自定义请求器时可参考使用。
在 lib/adapters/xhr.js#L158 中:
// 若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;
});
}
编写自定义适配器时,可复制此段代码,将内部取消操作替换为自身逻辑即可。
至此,若实现上述功能,即完成了一个标准适配器。
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 字段用于插入自定义适配器。使用自定义适配器有两种情况:
1. 完全只使用自定义适配器;
2. 在特定情况下使用自定义适配器,其他情况仍使用 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 } });
},
});
使用自定义适配器 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的详细内容,更多关于axios自定义适配器adapter的资料请关注本站其它相关文章!