介绍
Axios 是一个流行的基于 Promise 的 JavaScript HTTP 客户端,用于在浏览器和 Node.js 中发送 HTTP 请求。本文将引导您使用 TypeScript 手写 Axios 的简化版本,以帮助您更好地理解其内部工作原理。
环境设置
首先,确保您的开发环境中已安装 Node.js 和 TypeScript。使用以下命令进行安装:
npm install -g typescript
项目初始化
创建一个新的文件夹并进入该文件夹:
mkdir axios-clone
cd axios-clone
使用以下命令初始化 TypeScript 项目:
tsc --init
这将在项目根目录中生成一个 tsconfig.json
文件。
创建入口文件
在项目根目录中创建一个名为 index.ts
的文件,这将是我们的 Axios 入口文件。
import Axios from "./axios";
import { AxiosInstance } from "./types";
import {CancelToken,isCancel} from './cancel';
// 可以创建一个axios实例
// 定义一个类的时候,一个类的原型,Axios.prototype 一个类的实例
function createInstance() :AxiosInstance{
let context:Axios<any> = new Axios();
let instance = Axios.prototype.request.bind(context);
instance = Object.assign(instance, Axios.prototype, context);
return instance as AxiosInstance;
}
let axios = createInstance();
axios.CancelToken = new CancelToken();
axios.isCancel = isCancel;
export default axios;
export * from './types';
定义Axios类
这段代码是一个使用TypeScript编写的简化版Axios类。Axios是一个流行的HTTP请求库,用于在浏览器和Node.js中发送HTTP请求。以下是这段代码的介绍:
export default class Axios<T> {
public defaults: AxiosRequestConfig = defaults;
public interceptors = {
request: new AxiosInterceptorManager<AxiosRequestConfig>(),
response: new AxiosInterceptorManager<AxiosResponse<T>>()
}
/**
* 发起一个HTTP请求
* @param config 请求配置
* @returns Promise 包含请求配置或响应对象的Promise
*/
request(config: AxiosRequestConfig): Promise<AxiosRequestConfig | AxiosResponse<T>> {
// 对请求配置进行处理
// 合并默认headers和传入的headers
config.headers = { ...this.defaults.headers, ...config.headers };
// 如果定义了请求数据的转换函数,对数据进行转换
if (config.transformRequest && config.data) {
config.data = config.transformRequest(config.data, config.headers);
}
// 构建拦截器链
const chain: Interceptor<any>[] = [{
onFulfilled: this.dispatchRequest,
onRejected: undefined
}];
// 添加请求拦截器到拦截器链
this.interceptors.request.interceptor.forEach(interceptor => {
interceptor && chain.unshift(interceptor);
});
// 添加响应拦截器到拦截器链
this.interceptors.response.interceptor.forEach(interceptor => {
interceptor && chain.push(interceptor);
});
let promise: Promise<any> = Promise.resolve(config);
// 依次执行拦截器链中的拦截器
while (chain.length) {
const { onFulfilled, onRejected } = chain.shift()!;
promise = promise.then(onFulfilled, onRejected);
}
return promise;
}
/**
* 发送请求的方法
* @param config 请求配置
* @returns Promise 包含响应对象的Promise
*/
dispatchRequest<T>(config: AxiosRequestConfig): Promise<AxiosResponse<T>> {
return new Promise<AxiosResponse<T>>((resolve, reject) => {
// 创建XMLHttpRequest对象
const request = new XMLHttpRequest();
// 获取请求配置中的信息
let { method, url, params, headers, data, timeout } = config;
// 处理URL中的查询参数
if (params) {
let paramsStr = Object.keys(params).map(key => `${key}=${params[key]}`).join('&');
url += (url!.indexOf('?') === -1 ? '?' : '&') + paramsStr;
}
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,
config,
request: 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]);
}
}
}
}
}
let body: string | null = null;
// 发送请求数据
if (data) {
request.send(data);
body = JSON.stringify(data);
}
// 设置超时时间
if (timeout) {
request.timeout = timeout;
request.ontimeout = function () {
reject(`Timeout of ${timeout} ms exceeded`);
}
}
// 处理请求取消
if (config.CancelToken) {
config.CancelToken.then((message: string) => {
request.abort();
reject(message);
});
}
// 处理请求错误
request.onerror = function () {
reject("net::ERR_INTERNET_DISCONNECTED");
};
request.send(body);
});
定义CancelToken类
export class Cancel{
message?:string;
constructor(message?:string){
this.message = message;
}
}
export function isCancel(value:any){
return value instanceof Cancel;
}
export class CancelToken{
public resolve: any;
source(){
return {
token: new Promise((resolve) => {
this.resolve = resolve;
}),
cancel: (message:string)=>{
this.resolve(new Cancel(message));
}
}
}
}
定义defaults接口
let defaults:AxiosRequestConfig = {
method: "get",
timeout: 0,
headers: {
common: {
Accept: "application/json,text/plain,*/*"
}
},
transformRequrest: function (data: any, headers: any): 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 => {
defaults.headers![method] = {};
});
let postStyleMethods = ["post", "put", "patch"];
postStyleMethods.forEach(method => {
defaults.headers![method] = {
"Content-Type": "application/x-www-form-urlencoded"
};
});
let allMethods = [...getStyleMethods, ...postStyleMethods];
完善类型定义
在 types.ts
文件中,我们需要定义 AxiosRequestConfig
和 AxiosResponse
的类型。
import AxiosInterceptorManager from './axiosInterceptorManager';
export type Methods = 'get' | 'GET' | 'post' | 'POST' | 'put' | 'PUT' | 'delete' | 'DELETE' | 'options' | 'OPTIONS' | 'head' | 'HEAD' | 'patch' | 'PATCH';
export interface AxiosRequestConfig{
url?: string;
method?: Methods;
params?: any;
headers?: Record<string,any>;
data?: any;
timeout?: number;
transformRequrest?:(data:any,hedaers:any)=>any;
transformResponse?:(data:any)=>any;
CancelToken?:any;
}
export interface AxiosInstance{
<T = any>(config: AxiosRequestConfig): Promise<AxiosResponse<T>>;
interceptors: {
request: AxiosInterceptorManager<AxiosRequestConfig>;
response: AxiosInterceptorManager<AxiosResponse>;
},
CancelToken?:any;
isCancel?:any;
}
export interface AxiosResponse<T = any>{
data: T;
status: number;
statusText: string;
headers?: Record<string,any>;
config?: AxiosRequestConfig;
request?: XMLHttpRequest;
}
定义AxiosInterceptorManager类
interface onFulfilled<V>{
(value:V):V | Promise<V>;
}
interface onRejected{
(error:any):any;
}
export interface Interceptor <V>{
onFulfilled?: onFulfilled<V>;
onRejected?: onRejected;
}
export default class AxiosInterceptorManager<V> {
public interceptor:Array<Interceptor<V> | null> = []
// 用来存储拦截器
use(onFulfilled?:onFulfilled<V>,onRejected?:onRejected):number{
this.interceptor.push({
onFulfilled,
onRejected
})
return this.interceptor.length - 1;
}
eject(id:number):void{
if(this.interceptor[id]){
this.interceptor[id] = null;
}
}
}
测试代码
在项目根目录下创建一个 test.ts
文件,编写测试代码:
import axios from './index'
const baseUrl = 'https://api.wmdb.tv/api/v1/top';
interface Data{
type:string,
skip:number,
limit:number,
lang:string,
name:string
}
let data:Data = {
type:'Imdb',
skip:0,
limit:50,
lang:'Cn',
name:"loser"
}
axios.interceptors.request.use((config:AxiosRequestConfig)=>{
config.params.name += 1
return config;
})
axios({
method:'get',
url:baseUrl,
params:data,
}).then((res:AxiosResponse)=>{
console.log(res);
})