在上两期,咱们解说了 Axios
的源码:
- Axios 源码解读 —— request 篇
- Axios 源码解读 —— 网络申请篇
明天,咱们将实现一个繁难的 Axios
,用于在 Node
端实现网络申请,并反对一些根底配置,比方 baseURL、url、申请办法、拦截器、勾销申请...
本次实现所有的源码都放在 这里,感兴趣的能够看看。
Axios 实例
本次咱们将应用 typescript + node
来实现相干代码,这样对大家了解代码也会比拟清晰。
这里,先来实现一个 Axios
类吧。
type AxiosConfig = { url: string; method: string; baseURL: string; headers: {[key: string]: string}; params: {}; data: {}; adapter: Function; cancelToken?: number;}class Axios { public defaults: AxiosConfig; public createInstance!: Function; constructor(config: AxiosConfig) { this.defaults = config; this.createInstance = (cfg: AxiosConfig) => { return new Axios({ ...config, ...cfg }); }; }}const defaultAxios = new Axios(defaultConfig);export default defaultAxios;
在下面,咱们次要是实现了 Axios
类,应用 defaults
存储默认配置,同时申明了 createInstance
办法。该办法会创立一个新的 Axios
实例,并且会继承上一个 Axios
实例的配置。
申请办法
接下来,咱们将对 https://mbd.baidu.com/newspage/api/getpcvoicelist
发动一个网络申请,将响应返回的数据输入在控制台。
咱们发动申请的语法如下:
import axios from './Axios';const service = axios.createInstance({ baseURL: 'https://mbd.baidu.com'});(async () => { const reply = await service.get('/newspage/api/getpcvoicelist'); console.log(reply);})();
request 办法
咱们先来给咱们的 Axios
类加上一个 request
和 get
办法吧。
import { dispatchRequest } from './request';class Axios { //... public request(configOrUrl: AxiosConfig | string, config?: AxiosConfig) { if (typeof configOrUrl === 'string') { config!.url = configOrUrl; } else { config = configOrUrl; } const cfg = { ...this.defaults, ...config }; return dispatchRequest(cfg); } public get(configOrUrl: AxiosConfig | string, config?: AxiosConfig) { return this.request(configOrUrl, {...(config || {} as any), method: 'get'}); }}
这里 request
办法的实现和 axios
自带的办法差异性不大。
当初,咱们来编辑发动实在申请的 dispatchRequest
办法吧。
export const dispatchRequest = (config: AxiosConfig) => { const { adapter } = config; return adapter(config);};
和 axios
一样,调用了配置中的 adapter
来发动网络申请,而咱们在 defaultConfig
中配置了默认的 adapter
。(如下)
const defaultConfig: AxiosConfig = { url: '', method: 'get', baseURL: '', headers: {}, params: {}, data: {}, adapter: getAdapter()};
adapter 办法
接下来,咱们重点来看看咱们的 adapter
实现即可。
// 这里偷个懒,间接用一个 fetch 库import fetch from 'isomorphic-fetch';import { AxiosConfig } from './defaults';// 检测是否为超链接const getEffectiveUrl = (config: AxiosConfig) => /^https?/.test(config.url) ? config.url : config.baseURL + config.url;// 获取 query 字符串const getQueryStr = (config: AxiosConfig) => { const { params } = config; if (!Object.keys(params).length) return ''; let queryStr = ''; for (const key in params) { queryStr += `&${key}=${(params as any)[key]}`; } return config.url.indexOf('?') > -1 ? queryStr : '?' + queryStr.slice(1);};const getAdapter = () => async (config: AxiosConfig) => { const { method, headers, data } = config; let url = getEffectiveUrl(config); url += getQueryStr(config); const response = await fetch(url, { method, // 非 GET 办法才发送 body body: method !== 'get' ? JSON.stringify(data) : null, headers }); // 组装响应数据 const reply = { data: await response.json(), status: response.status, statusText: response.statusText, headers: response.headers, config: config, }; return reply;};export default getAdapter;
在这里,咱们的实现相对来说比拟简陋。简略来说就是几步
- 组装 url
- 发动申请
- 组装响应数据
看看成果
当初来控制台运行一下咱们的代码,也就是上面这,看看控制台输入吧。
import axios from './Axios';const service = axios.createInstance({ baseURL: 'https://mbd.baidu.com'});(async () => { const reply = await service.get('/newspage/api/getpcvoicelist'); console.log(reply);})();
从上图能够看出,咱们的 axios
最根底的性能曾经实现了(尽管偷了个懒用了 fetch
)。
接下来,咱们来欠缺一下它的能力吧。
拦截器
当初,我想要让我的 axios
领有增加拦截器的能力。
- 我将在申请处增加一个拦截器,在每次申请前加上一些自定义
headers
。 - 我将在响应处增加一个拦截器,间接取出响应的数据主体(
data
)和配置信息(config
),去除多余的信息。
代码实现如下:
// 增加申请拦截器service.interceptors.request.use((config: AxiosConfig) => { config.headers.test = 'A'; config.headers.check = 'B'; return config;});// 增加响应拦截器service.interceptors.response.use((response: any) => ({ data: response.data, config: response.config }));
革新 Axios 类,增加 interceptors
咱们先来创立一个 InterceptorManager
类,用于治理咱们的拦截器。(如下)
class InterceptorManager { private handlers: any[] = []; // 注册拦截器 public use(handler: Function): number { this.handlers.push(handler); return this.handlers.length - 1; } // 移除拦截器 public eject(id: number) { this.handlers[id] = null; } // 获取所有拦截器 public getAll() { return this.handlers.filter(h => h); }}export default InterceptorManager;
定义好了拦截器后,咱们须要在 Axios
类中加上拦截器 —— interceptors
,如下:
class Axios { public interceptors: { request: InterceptorManager; response: InterceptorManager; } constructor(config: AxiosConfig) { // ... this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() } }}
接下来,咱们在 request
办法中解决一下这些拦截器的调用吧。(如下)
public async request(configOrUrl: AxiosConfig | string, config?: AxiosConfig) { if (typeof configOrUrl === 'string') { config!.url = configOrUrl; } else { config = configOrUrl; } const cfg = { ...this.defaults, ...config }; // 将拦截器与实在申请合并在一个数组内 const requestInterceptors = this.interceptors.request.getAll(); const responseInterceptors = this.interceptors.response.getAll(); const handlers = [...requestInterceptors, dispatchRequest, ...responseInterceptors]; // 应用 Promise 将数组串联调用 let promise = Promise.resolve(cfg); while (handlers.length) { promise = promise.then(handlers.shift() as any); } return promise;}
这里次要是将拦截器和实在的申请合并成一个数组,而后再应用 Promise
进行串联。
这里发现了一个本人还不晓得的Promise
知识点,在Promise.resolve
中,不须要显式返回一个Promise
对象,Promise
外部会将返回的值包装成一个Promise
对象,反对.then
语法调用。
当初,再运行一下咱们的代码,看看加上拦截器后的运行成果吧。(如下图)
从上图能够看出,返回的内容中,只剩下了 data
和 config
字段(响应拦截器)。并且在 config
字段中也能够看到咱们在 申请拦截器
中增加的自定义 headers
也起作用啦!
勾销申请
最初,咱们来实现 CancelToken
类,用于勾销 axios
申请。
在理论利用中,我常常会应用 CancelToken
来自动检测反复申请(来源于频繁点击),而后勾销掉更早的申请,仅应用最初一次申请作为无效申请。
所以,CancelToken
对我而言其实是个十分青睐的性能,它自身的实现并不简单,咱们上面就开始来实现它吧。
咱们先看看调用形式吧,上面我将在发动申请后 10ms 后(利用 setTimeout
),将申请勾销。也就是说,只有 10ms 内实现的申请能力胜利。
import axios, { CancelToken } from './Axios';// ...(async () => { const source = CancelToken.source(); // 10ms 后,勾销申请 setTimeout(() => { source.cancel('Operation canceled by the user.'); }, 10); const reply = await service.get('/newspage/api/getpcvoicelist', { cancelToken: source.token }); console.log(reply);})();
咱们先来理一理思路。
首先,咱们应用了 CancelToken.source()
获取了一个 cancelToken
,并传给了对应的申请函数。
接下来,咱们应该是应用这个 token
进行查问,查问该申请是否被勾销,如果被勾销则抛出谬误,完结这次申请。
CancelToken
ok,思路曾经清晰了,接下来就开始实现吧,先从 CancelToken
开始吧。
class CancelError extends Error { constructor(...options: any) { super(...options); this.name = 'CancelError'; }}class CancelToken { private static list: any[] = []; // 每次返回一个 CancelToken 实例,用于勾销申请 public static source(): CancelToken { const cancelToken = new CancelToken(); CancelToken.list.push(cancelToken); return cancelToken; } // 通过检测是否有 message 字段来确定该申请是否被勾销 public static checkIsCancel(token: number | null) { if (typeof token !== 'number') return false; const cancelToken: CancelToken = CancelToken.list[token]; if (!cancelToken.message) return false; // 抛出 CancelError 类型,在后续申请中解决该类型谬误 throw new CancelError(cancelToken.message); } public token = 0; private message: string = ''; constructor() { // 应用列表长度作为 token id this.token = CancelToken.list.length; } // 勾销申请,写入 message public cancel(message: string) { this.message = message; }}export default CancelToken;
CancelToken
基本上实现了,它的次要性能就是应用一个 CancelToken
实例对应一个须要做解决的申请,而后在已勾销的申请中抛出了一个 CancelError
类型的抛错。
解决 CancelError
接下来,咱们须要在对应的申请处(dispatchRequest
)增加勾销申请检测,最初再加上一个对应的响应拦截器解决对应谬误即可。
export const dispatchRequest = (config: AxiosConfig) => { // 在发动申请前,检测是否勾销申请 CancelToken.checkIsCancel(config.cancelToken ?? null); const { adapter } = config; return adapter(config).then((response: any) => { // 在申请胜利响应后,检测是否勾销申请 CancelToken.checkIsCancel(config.cancelToken ?? null); return response; });};
因为咱们的拦截器实现的太毛糙,并没有增加失败响应拦截器(本应在这里解决),所以我这里间接将整个申请包裹在 try ... catch
中解决。
try { const reply = await service.get('/newspage/api/getpcvoicelist', { cancelToken: source.token }); console.log(reply);} catch(e) { if (e.name === 'CancelError') { // 如果申请被勾销,则不抛出谬误,只在控制台输入提醒 console.log(`申请被勾销了, 勾销起因: ${e.message}`); return; } throw e;}
接下来,咱们运行咱们的程序,看看控制台输入吧!(如下图)
功败垂成!
小结
到这里,咱们的简易版 axios
就曾经实现啦。
它能够用于在 Node
端实现网络申请,并反对一些根底配置,比方 baseURL、url、申请办法、拦截器、勾销申请...
然而,还是有很多不够欠缺的中央,感兴趣的小伙伴能够找到上面的源码地址,持续往下续写。
源码地址,倡议练习
最初一件事
如果您曾经看到这里了,心愿您还是点个赞再走吧~
您的点赞是对作者的最大激励,也能够让更多人看到本篇文章!
如果感觉本文对您有帮忙,请帮忙在 github 上点亮 star
激励一下吧!