In the last two issues, we explained the source code Axios
Today, we will implement a simple Axios
, for Node
implement network requests end, and to support some basic configuration, such baseURL, url, request method, interceptors, cancellation request ...
All the source code of this implementation is placed in here , you can take a look if you are interested.
Axios instance
This time we will use typescript + node
to implement the relevant code, so that it will be clearer for everyone to understand the code.
Here, let's implement a Axios
class first.
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;
Above, we mainly implement the Axios
class, use defaults
store the default configuration, and declare the createInstance
method. This method creates a new Axios
instance and inherits the configuration of the Axios
request method
Next, we will https://mbd.baidu.com/newspage/api/getpcvoicelist
output the data returned by the response to the console.
The syntax for our request is as follows:
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 method
Let's first add a request
and get
method Axios
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'});
}
}
The implementation of the request
method here is not very different from the method that comes with axios
Now, let's edit the 061eb8f5cc5a6d method dispatchRequest
export const dispatchRequest = (config: AxiosConfig) => {
const { adapter } = config;
return adapter(config);
};
And axios
like to call the configuration adapter
to initiate network requests, and we defaultConfig
configured with a default of adapter
. (as follows)
const defaultConfig: AxiosConfig = {
url: '',
method: 'get',
baseURL: '',
headers: {},
params: {},
data: {},
adapter: getAdapter()
};
adapter method
Next, let's focus on our adapter
implementation.
// 这里偷个懒,直接用一个 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;
Here, our implementation is relatively rudimentary. Just a few steps
- assembly url
- make a request
- Assemble response data
see the effect
Now come to the console to run our code, which is the following, and look at the console output.
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);
})();
As can be seen from the above figure, the axios
have been implemented (although I stole it and used fetch
).
Next, let's improve its capabilities.
interceptor
Now, I want to have the axios
add interceptors to my 061eb8f5cc5c00.
- I'm going to add an interceptor at the request with some custom
headers
before each request. - I will add an interceptor to the response to directly take out the data body (
data
) and configuration information (config
) of the response to remove redundant information.
The code is implemented as follows:
// 添加请求拦截器
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 }));
Modify the Axios class and add interceptors
Let's start by creating a InterceptorManager
class to manage our interceptors. (as follows)
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;
After defining the interceptor, we need to add the interceptor - interceptors
Axios
class, as follows:
class Axios {
public interceptors: {
request: InterceptorManager;
response: InterceptorManager;
}
constructor(config: AxiosConfig) {
// ...
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
}
}
Next, let's handle these interceptor calls request
(as follows)
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;
}
The main thing here is to combine the interceptor and the real request into an array, and then use Promise
for concatenation.
Promise
knowledge point that I don't know yetPromise.resolve
, there is no need to explicitly return aPromise
object.Promise
internally wraps the returned value into aPromise
object, which supports.then
syntax calls.
Now, run our code again to see the effect of adding the interceptor. (As shown below)
As can be seen from the above figure, only the data
and config
fields (response interceptors) are left in the returned content. And config
field, we can also see in request to add the interceptor custom
headers
also play it!
cancel the request
Finally, let's implement the CancelToken
class, which is used to cancel the axios
request.
In practical applications, I often use CancelToken
to automatically detect duplicate requests (from frequent clicks), then cancel earlier requests and use only the last request as a valid request.
Therefore, CancelToken
is actually a very favorite function for me. Its implementation is not complicated. Let's start to implement it below.
Let's take a look at the calling method first. Next, I will cancel the setTimeout
That is, only requests completed within 10ms can succeed.
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);
})();
Let's start with a rationale.
First, we used CancelToken.source()
get a cancelToken
and passed it to the corresponding request function.
Next, we should use this token
to query to see if the request has been canceled. If it is canceled, an error will be thrown to end the request.
CancelToken
ok, the idea is clear, let's start implementing it, CancelToken
start with 061eb8f5cc5e5b.
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
is basically completed. Its main function is to use an CancelToken
to correspond to a request that needs to be processed, and then throw an CancelError
type 061eb8f5cc5ec1 in the canceled request.
Handling CancelError
Next, we need to dispatchRequest
), and finally add a corresponding response interceptor to handle the corresponding error.
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;
});
};
Since our interceptor is too rough to add a failure response interceptor (which should be handled here), I directly wrap the entire request in try ... catch
for processing.
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;
}
Next, let's run our program and see the console output! (As shown below)
You're done!
summary
At this point, our simple version axios
has been completed.
It can be used Node
side, and supports some basic configurations, such as baseURL, url, request method, interceptor, cancel request...
However, there are still many imperfections. Interested partners can find the source code address below and continue to write.
source code address, it is recommended to practice
one last thing
If you have seen this, I hope you will give a like and go~
Your likes are the greatest encouragement to the author, and can also allow more people to see this article!
If you think this article is helpful to you, please help to light up star
github to encourage it!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。