手写Axios源码——(三)

手写Axios源码 - 三

实现defaults默认配置项合并 + 请求响应转化 + cancelToken请求取消

  1. 实现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])
          	}
        }
      }
      // ....
	}
}
  1. 转化请求响应:
    转化请求响应就是在配置中添加两个请求响应的转化方法,在请求发送之前将请求的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}`)
          }
        }
      }
	// ...
}
  1. 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);
    })
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值