手写Axios源码 - 三
实现defaults默认配置项合并 + 请求响应转化 + cancelToken请求取消
- 实现defaults默认配置项合并:
默认配置的合并主要做一些common的headers的配置,将这些全局的默认配置合并到每个请求的headers里面去进行发送:
// 在Axios.ts文件中处理
// 设定axios请求默认参数:
// 这个默认配置的axios参数将会和传递进来的配置参数进行合并
let defaults: AxiosRequestConfig = {
method: 'get',
timeout: 0,
headers: {
common: { // 针对所有的请求生效
name: "requestName",
accept: "application/json"
}
},
// 转换请求和转换响应, 这个需要放到axios请求的配置中去,这里只是调试时的放置
transformRequest: (data: any, headers: any) => {
headers['common']['content-type'] = 'application/json';
return JSON.stringify(data)
},
transformResponse: (response: any) => {
return response.data;
}
}
// ....
let getStyleMethods = ['get', 'head', 'delete', 'options']; // get风格的请求
getStyleMethods.forEach((method: string) => {
defaults.headers![method] = {};
})
let postStyleMethods = ['put', 'post', 'patch']; // post风格的请求
postStyleMethods.forEach((method: string) => {
defaults.headers![method] = {
'content-type': 'application/json' // 请求头格式`
}
})
let allMethods = [...getStyleMethods, ...postStyleMethods];
export default class Axios<T> {
request(config: AxiosRequestConfig): Promise<AxiosRequestConfig | AxiosResponse<T>> {
// ...
// ----合并默认配置项: 这里只是简单的合并处理
config.headers = Object.assign(this.defaults.headers, config.headers)
// ...
}
dispatchRequest<T>(config: AxiosRequestConfig): Promise<AxiosResponse<T>> {
// ....
// 发送请求前判断是否存在header值:
if (headers) {
for (let key in headers) {
// common表示所有的请求方法都生效,或者说key是一个方法名, 此时这些共有的默认配置就需要单独设置到headers上
if (key === "common" || allMethods.includes(key)) {
if (key === "common" || key === config.method) {
for (let key2 in headers[key]) {
request.setRequestHeader(key2, headers[key][key2])
}
}
} else {
request.setRequestHeader(key, headers[key])
}
}
}
// ....
}
}
- 转化请求响应:
转化请求响应就是在配置中添加两个请求响应的转化方法,在请求发送之前将请求的headers和data做一些处理,在响应时调用响应转化对响应内容做一些转化处理:
// 修改请求配置类型: types.ts文件
export interface AxiosRequestConfig {
url?: string;
method?: Methods;
params?: any;
headers?: Record<string, any>;
data?: Record<string, any>;
timeout?: number,
transformRequest?: (data: any, headers: any) => any; // 转换请求和转换响应
transformResponse?: (data: any) => any;
}
// 在Axios.ts中的相应位置执行该方法:
request(config: AxiosRequestConfig): Promise<AxiosRequestConfig | AxiosResponse<T>> {
// ...
// --- 执行请求转换:
if (config.transformRequest && config.data) {
config.data = config.transformRequest(config.data, config.headers)
} else if (config.data && this.defaults.transformRequest) {
config.data = this.defaults.transformRequest(config.data, config.headers)
}
// ...
}
dispatchRequest<T>(config: AxiosRequestConfig): Promise<AxiosResponse<T>> {
// ...
request.onreadystatechange = function () {
// 指定状态变更监听函数
if (request.readyState === 4) {
if (request.status >= 200 && request.status < 300) {
let response: AxiosResponse<T> = {
data: request.response ? request.response : request.responseText,
status: request.status,
statusText: request.statusText,
headers: parseHeader(request.getAllResponseHeaders()),
config,
request
}
// -- 处理响应响应转换:
if (config.transformResponse) {
response = config.transformResponse(response);
}
resolve(response)
} else {
reject(`Error: Request failed with status code ${request.status}`)
}
}
}
// ...
}
- cancelToken可以在发送请求的中途取消请求, 主要通过一个Promise实现在特定时候出发request.abort()方法取消请求:
// 新建cancel.ts文件实现CancelToken和Cancel类:
export class Cancel{
messgae: string;
constructor(message: string) {
this.messgae = message;
}
}
// 该方法用于判断当前的请求错误是否是取消请求产生的
export function isCancel(error: any) {
return error instanceof Cancel;
}
export class CancelToken {
public resolve: any;
source() {
let that = this;
return {
token: new Promise(function (resolve) {
that.resolve = resolve;
}),
cancel: (message: string) => { // 通过调用cancel方法将token中的promise resolve掉,使得在promise之后的then中的取消请求得以执行
that.resolve(new Cancel(message))
}
}
}
}
// 在Axios.ts文件中添加处理:
dispatchRequest<T>(config: AxiosRequestConfig): Promise<AxiosResponse<T>> {
// ...
if (config.cancelToken) {
config.cancelToken.then((message: string) => {
request.abort(); // 取消请求,这里的cancleToken就是前面那个promise
reject(message)
})
}
// ...
}
// 在index.ts文件的axios实例上添加CancelToken和isCancel属性:
import { isCancel, CancelToken } from "./cancel"
// ...
axios.CancelToken = new CancelToken();
axios.isCancel = isCancel;
// ...
至此, 一款简易版的axios就实现得差不多了,文章最后附上Axios.ts文件得完整代码,其余文件均变动不大,大家可以自行添加:
import { AxiosRequestConfig, AxiosResponse } from "./types"
import AxiosInterceptorManager, { Interceptor } from "./AxiosInterceptorManager"
import qs from "qs";
import parseHeader from "parse-headers";
let defaults: AxiosRequestConfig = {
method: 'get',
timeout: 0,
headers: {
common: {
name: "requestName",
accept: "application/json"
}
},
transformRequest: (data: any, headers: any) => {
headers['common']['content-type'] = 'application/json';
return JSON.stringify(data)
},
transformResponse: (response: any) => {
return response.data;
}
}
let getStyleMethods = ['get', 'head', 'delete', 'options'];
getStyleMethods.forEach((method: string) => {
defaults.headers![method] = {};
})
let postStyleMethods = ['put', 'post', 'patch'];
postStyleMethods.forEach((method: string) => {
defaults.headers![method] = {
'content-type': 'application/json'
}
})
let allMethods = [...getStyleMethods, ...postStyleMethods]
export default class Axios<T> {
public defaults: AxiosRequestConfig = defaults;
public interceptors = {
request: new AxiosInterceptorManager<AxiosRequestConfig>(),
response: new AxiosInterceptorManager<AxiosResponse<T>>()
}
request(config: AxiosRequestConfig): Promise<AxiosRequestConfig | AxiosResponse<T>> {
config.headers = Object.assign(this.defaults.headers, config.headers)
if (config.transformRequest && config.data) {
config.data = config.transformRequest(config.data, config.headers)
} else if (config.data && this.defaults.transformRequest) {
config.data = this.defaults.transformRequest(config.data, config.headers)
}
const chain: Array<Interceptor<AxiosRequestConfig> | Interceptor<AxiosResponse<T>>> = [{
onFulfilled: this.dispatchRequest,
onRejected: (error: any) => error
}]
this.interceptors.request.interceptors.forEach((interceptor: Interceptor<AxiosRequestConfig> | null) => {
interceptor && chain.unshift(interceptor)
})
this.interceptors.response.interceptors.forEach((interceptor: Interceptor<AxiosResponse<T>> | null) => {
interceptor && chain.push(interceptor)
})
let promise: any = Promise.resolve(config);
while (chain.length) {
const { onFulfilled, onRejected } = chain.shift()!;
promise = promise.then(onFulfilled, onRejected)
}
return promise;
}
dispatchRequest<T>(config: AxiosRequestConfig): Promise<AxiosResponse<T>> {
return new Promise<AxiosResponse<T>>(function (resolve, reject) {
let { method, url, params, headers, data, timeout } = config;
let request = new XMLHttpRequest();
if (params) {
params = qs.stringify(params);
url += ((url!.indexOf('?') > 0 ? '&' : '?') + params);
}
request.open(method!, url!, true);
request.responseType = "json";
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status >= 200 && request.status < 300) {
let response: AxiosResponse<T> = {
data: request.response ? request.response : request.responseText,
status: request.status,
statusText: request.statusText,
headers: parseHeader(request.getAllResponseHeaders()),
config,
request
}
if (config.transformResponse) {
response = config.transformResponse(response);
}
resolve(response)
} else {
reject(`Error: Request failed with status code ${request.status}`)
}
}
}
if (headers) {
for (let key in headers) {
if (key === "common" || allMethods.includes(key)) {
if (key === "common" || key === config.method) {
for (let key2 in headers[key]) {
request.setRequestHeader(key2, headers[key][key2])
}
}
} else {
request.setRequestHeader(key, headers[key])
}
}
}
let body: string | null = null;
if (data) {
body = JSON.stringify(data)
}
request.onerror = function () {
reject('net::ERR_INTERNET_DISCONNECTED');
}
if (timeout) {
request.timeout = timeout;
request.ontimeout = function () {
reject("Error: timeout~~")
}
}
if (config.cancelToken) {
config.cancelToken.then((message: string) => {
request.abort();
reject(message)
})
}
request.send(body);
})
}
}