手写 Axios 源码(使用 TypeScript)

介绍

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 文件中,我们需要定义 AxiosRequestConfigAxiosResponse 的类型。

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);

})
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值