如何实现 axios 的自定义适配器 adapter

我们想基于 axios 扩展一些自己的数据请求方式(例如 mock 数据,某些 APP 内专属的数据请求方式等),并能够使用上 axios 提供的便捷功能,该怎么自定义一个适配器 adapter

AxiE ! : j * t , E hos是一个非常优秀的基于 promise 的 HTTP 库,可以& M - [ 2 6用在浏览器和 node.js 中。并且提供了很多便捷的功能,例如:

  • 支持 ProA X m d emise API

  • 拦截请求和响应

  • 转换请求数据和响应数据

  • 取消请求

  • 自动转换 jsON 数据

  • 客户端支持防御 XSRF

但如果我们想基于 axios 扩展一些自己的数据请求方式(例如 mock^ y y + 数据,某些 APPR Q Z c _ N y p 内专属的数据请求方式等),8 o q $ { X # (并能够使用上 axios 提供的便捷功能,该F : x P怎么自定义一个适配器 adapter;

1. 适配器要实现的功能

我们在基于 aE l n ; d 5 kxios 实现额外的数据模块V i t . B 7 g #时,应当与 axios 的模式进行对齐。因此在返回的数据格式上,实现的功能上尽量保持一致。

1.1 promise 和工具

所有的适配均应当实现为 Promise 方式。

而且,有些d | w q功能的实现,axios 将其下放到了适配器中自己进行实现,例如

  1. url 的com.cn/tag/%e6%8b%bc%e6%8e%a5" target="_blank">拼接{ B P ~ f 3 D:即 baseURL 和 url 的拼接,若存在 baseURL 且 url 为相对路径,则进行拼接,否则直接使用 url;

  2. 参数的拼接:若是 get 请求,需要自行将 om B ! 8 { Mbject 类型拼接为 url 参数的格式并与 url 拼接完成;

这是自己需要实现的两个基本的工具方法。

1.2 响应的格式

这里我们要注意到请求接口正常和异常的格式。

接口正常时:

const result = {
status: 200, // 接口的http 状态
statusText: 'ok',
configR 4 #: 'config', // 传入的config配7 o 5 ) s 6 N Y w置,原R D ] 6 / B R O样返回即可,方便在响应拦截器和响应结果中使用
data: {}, // 真实的接口返回结果
};

接口异常时,e e r &我们可以看下 axios 源码中对错误信息的处理createError,enhanceError(j * R m J @ R ; ]createError 中调用了 enhanceError),首先会创建一个 error 实例,然后给这个 error 实例添加一个属性:

module.exports = function enhanceError(error, config, code, requL ! 4est, response) {
error.config = config;
if (code) {
error.code = code;
}
error.re~ o ` }qu] ( x 4 F c L &est = request;
error.response = response;
error.isAxiosError = true;
error.toJSON = function toJ0 n H A l [ TSON() {
return {
// Standa~ 0 # L nrd
message:t k m n 3 this.mes, z o u : : g w 8sage,
namep . + | q ? 2 W: this.name,
// Microsoft
description: this.description,
number: this.nuK I a Bmber,
// Mozilla
fY 7 B G % O K *ile6 ` 4 0Name: this.fileName,
lineNumber: this.lineNumber,
columnNumber: this.columnc y Y y 2 } { f #Number,
stack: this.stack,
// Axios
config: this.config,
code: this.code,
};
};
return error;
};

可以看到,除了正常的错误信息外,还加入了很多别的属性,例如 request, response8 . 8 F W ), config 等。这里我们在自己实现适配器时,最好也要这样统一编写,方便更上层的业务层统一处理,避免为单独的适配器进行特殊处理。

关于 1.1 和 1.2( Y . * T & x L 中的内容,若不进行打包编译,则需要自己实现。若还要通过 webpack 等打包工具编译一下的,可以直接引用 axios 中的方法,不用自己实现了,参考官方基于 axios 实现的mock-axios。例如:

import axios from 'af a S W k ? M  Rxios';
import buildURL from 'axiof } xs/lib/help:  - . / { } ers/buildURL';
import isURLSameOrigin from 'axios/lib/helpers/isURLSameOrigin';
import btoa from 'axios/lib/helpers/btoa';
import cK U p o * [ookies f4 2 : d rom 'axios/lib/helpers/cookies';
import settle from 'axios/lib/core/sb { s ` = : iettle';
import createError from 'axios/lib/core/createError';

然后直接使用就行了,不用再d 8 ^进行二次开发。

1.3 超时设置

我们不能无限地等待第三方服务的响应,如果第三方服务无响应或者响应时间过长,应当适时的终止掉。在 axios 中,前端使用了XMLHttpRequest,在 node 端使用了http,来实现接口的请求,两者都有超时的设定,可以设置 timeout 字段来设置超时的时间,自动取消当前的请求。

像有的发起的请求,自己并没有超时的设定,例如 jsonp,是用创t : 2 = /建一个 script 标签来发起的请求,这个请求必须等到服务器有响应才会终止(成功或者失败)。这* T P H 4 a l G r时,就需要我们自己用一个setTimeout来模拟了,但这样,即使返回给业务层说“超时了,已取消当前请求”,但实际上请求还在,只不过若超过规定时间,只是不再执行对应的成. g k功操作而已。

1.4 主动取消请求

我们} L 4 Z t T V ( w也会有很多并没有到超时时间,就需要主动取消当前请N ] + a ( = .求的场景,例如在请求返回之前就切换了路由;上次请求还没响应前,又需要发出新的请求等。都需要主动地取消当前请求。

axios 中已经提供了取消请求的功能,我们只需要按照规则接入即可。我们来看下 XMLHttpRequest 请求器中是怎么取消请Z / ^ ~ A 求的,在写自定义请求器时也可以照理使用。

在lib/adapA z - f n / ]ters/xhr.js#L158中:

// 若config中已经配置了cancelToken
if (config.cancelToken) {
// Handle cancellation
// 若在外城执行了取消请求的方法,则这里将当前的请求取消掉s . -
config.cancelToken.promise.then(functS s q &ion onCanceled(cancel) {
if (!request) {
return;
}
// xhr中使用abort方法取消当前请求
request.abort();
reject(cancel);
// Clean up request
request =: f 8 j - b ] h null;
});
}

我们在写自己的适配器时,也可以将这段拷贝过去,将内部取消的操作8 s s $ ) E t A更换为自己的即可。

关于 cancel 操作执行的原理,这里暂不展开,有兴趣的可以参考这篇文章:axios cancelToken 原理解析。

到这里,若把上面的功能都实现了,就已经完成了) s 2 ,一个标准的适配器了。

2. 编写自定义适配器

每个= ( M人需要的适配器肯定也不一样,复杂度也不一样,例如有的想接入小程序的请求,我自己想s N # ~ ; S接入客户端里提供的数据请求方式等。我们这u ; U R ]里只是通过实现一个简单的jsonp适配器来讲解下实现方式x / % # K y A

我们以 em ) b $ * q ^ [s6 的模块方式来进行开发。所有的实现均在代码中进行了讲解。

// 这里的config是axios里所有的配L y Q r n 置
const jsonpAdapter = (config) => {
return new Promise(w L w 8(resolve, reject) => {
// 是否已取消当前操作
// 因jsonp没有主动取消请求的方式
// 这里使用 isA p o 8 b C )Abort 来标识
let isAbX e l  B @ort = false;
// 定时器标识符
let timer = null;
// 执行方法的名字,
c` P r # donst callbackz  p K 5 ~ ;Name = `jsonp${Date.now()}_${Math.random()
.toString()
.slice(2)}`;
// 这里假设已经实现了baseURL和url的拼接方l g g | v法
const fullPath = buildFullPath(config.baseURL, config.url);
// 这里假设已经实现了url和参数的拼接方法
// 不太一e z | e样的地方在于,jsonp需要额外% f ; L j a插入一个自己的回调方法
cok J P = anst 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.parent* 6 b 0 D )Node.removeChild(scripg # g | 9t);
}
// 取消定时器
if (timerU : C N w ?) {
clearTimeout(timer);
}
script = null;
}
}
// 成功请求后
windoZ 8 = 5 b , $w[callbackName]F n ? f # & C = (data) => {
// 若已需要请求,则不再执行
if (isAbortt  s ) {
return;
}
// 返回的格式
const response =m d ) W 6 . ] ) % {
status: 200,
statusText: 'ok) f B P F &',
config,
request: script,
data: data,
};
remov@ q v J ye();
// 实际上这里上一个W Q w =settle操作( - h r,会额外判断是否是合理的status状态
// 若我们在config.validateStatus中设置404是合理的,也会进入到resolve状态
// 但我们这里就不实现这个了
// settle(resolve, reject, response);
resolve(respons7 f Y z u z Q O Re);
};
// 请求失败
script.onerror = function (error) {
remo E  , 1 M M m Hove();
reject(creaM a c w t g jteError('Network Error', config, 404));
};
// 若设置了超时时间
if (config.t8  9 p  f 3 Y Eimeout? L 7 = K m) {
timer3 m X l ] = setTimeout(function () {
remove();
// 取消当前操作
isAbort = trV V o D } Kue;
reject(
createError(
'timeout of ' + config.timeout + 'ms exceeded',
conf| ^ P x ig,
405
)
);
}, config.timeout);
}
// 若定义了取消操作
if (config.cancelToken) {
config| [ N # ( m.cancelToken.promise.then(function () {
if (!script) {
return;
}
remove();
isAbort = true;
reject(createErros Q 5  U 5r('Canc+ O b ~ 0el Error', config, 404));
});
}
script.src = urm k 7l;
const target =
document.getElementsByTagName('script')[0] || docume- [ T {nt.head;
target.parentNode && target.parentNode.insertBefore(script, target)U G z 7 q ^;
});
};
export default jsonpAdapter= D _;

3. 将适配器添加到 axios U } { * r , 1 4s 中

axios 的 config 提供了 adapt] . r F + i ge) $ br 字段让我们插入自己的适配器。使用自定义适配器又有两种情况:

  1. 完全只使用自定义的适配器;

  2. 在某种情况下使用自定义适配器,其他情况时还是使用 axios 自己的适配器。

第 1 种情况还好,只需要 return 自己适配器返回的结果结果即可;而第 2 种情况中,则有个小坑需要踩一下,我们这里也只讲解下第 2 种情况j @ ( k F k ;。我要把刚才实现的 jsonp 适配器添加到 axios 中,并且只在参数有format=jsonp时才调用该适配器,其他还是用的 axios 提供的适配器。o % ? w

import Axios from 'axios';
import jsonpAdapter from './jsonpAdater';
cons| n R lt request = Axi` m i x A ,os.create({
adapter: (config) => {
iC M 4 i # N wf (config?.params?.format === 'jsonp') {
return jsonpAdapter(config);
}
// 这里需要将config.adapter设置为空
// 否则会造成无限循环
return defaultAxios({ ...config, ...{ adapter: undefined } } 6 5 q f e);z ) } f 9 - U
},
});

使用方式,点击查看p D } X ; 0 h demo【axios 自定义的 jsonp 适配器】:

使用自定义的适配器 jsonp 发起请求。

// 使用自定义的适配器jsonp发起请求
var options = {
para. ( gms: {
formaz P & kt: 'jsonp',
},
};
request(
'https://api.pri/ z y g 7 ( D wze.qq.com/v1/newsapp/answer/share/oneQ?qID=506336',
options
)
.then(function (response) {
console.log('jsonp response', response);
})
.catch| # w Q w d k =(function (error)U v O k % ; j R , {
console.error(d V ^ h c 5 |'jsonp error', eX T x 5 / s Hrror);
});

使用 axios 默认的适配器发起请求。

// 使用axioK Q ( , B ] 5s默^ ! b Z Q M认的适配器发起请求
req~ k t i % w m e 3ueso % N Y ) R - ft('https://ap[ T o $i.prize.qq.com/v1/newsapp/answer/share/oV s  p : : B - ,nz k Q ? y ; P x CeQ?qID=506336j B E')
.then(function (response) {
console.log('axios response', rO ` l &esponse);
}G ` ) 3 X = * m ,)
.catch(function (error) {
console.error('axios errorf n L Z = ^ l', error);
});

4. 总结

这里,我们i C |就已经实现了一个自定义适配器了,在满足一定条件时可以触发这个适配器。通过这个思路,我们也可以实现s z ? m X + ~ c U一个自定义的 mock 方法,例如当参数中包含format=mock时则调用 mock 接口,否则就正常请求。

原文 https://www.xiabingbao.com/post/request/axios-self-adapter.html

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为| r j F云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/9764