使用原生XMLHttpRequest实现Axios核心功能

概述

Axios是一个基于Promise的HTTP客户端,在浏览器和Node.js中均可使用。虽然Axios在浏览器端基于XMLHttpRequest实现,但它提供了更简洁的API和强大的功能,如拦截器、自动JSON转换和请求取消等。

本文将探讨如何使用原生XMLHttpRequest实现Axios的核心功能,帮助开发者深入理解网络请求的底层原理。

XMLHttpRequest基础

XMLHttpRequest (XHR) 是浏览器提供的API,用于在客户端和服务器之间传输数据。它支持各种HTTP方法(GET、POST、PUT、DELETE等)并能够处理多种数据格式。

基本使用示例:

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.responseText);
  }
};
xhr.send();

实现Axios核心功能

1. 创建请求函数

首先,我们需要创建一个类似axios的函数,它能够接受配置对象并返回Promise:

function request(config) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    
    // 设置请求方法和URL
    xhr.open(config.method || 'GET', config.url, true);
    
    // 设置请求头
    if (config.headers) {
      Object.keys(config.headers).forEach(key => {
        xhr.setRequestHeader(key, config.headers[key]);
      });
    }
    
    // 处理超时
    if (config.timeout) {
      xhr.timeout = config.timeout;
    }
    
    // 处理响应
    xhr.onload = function() {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve({
          data: parseResponse(xhr),
          status: xhr.status,
          statusText: xhr.statusText,
          headers: parseHeaders(xhr),
          config: config
        });
      } else {
        reject(createError(xhr, config));
      }
    };
    
    // 处理错误
    xhr.onerror = function() {
      reject(createError(xhr, config, 'Network Error'));
    };
    
    xhr.ontimeout = function() {
      reject(createError(xhr, config, `Timeout of ${config.timeout}ms exceeded`));
    };
    
    // 发送请求数据
    xhr.send(config.data || null);
  });
}

2. 实现响应数据处理

Axios自动根据响应内容类型解析数据,我们需要实现类似功能:

function parseResponse(xhr) {
  const contentType = xhr.getResponseHeader('Content-Type');
  const response = xhr.responseText;
  
  if (contentType && contentType.includes('application/json')) {
    try {
      return JSON.parse(response);
    } catch (e) {
      return response;
    }
  }
  
  return response;
}

function parseHeaders(xhr) {
  const headers = {};
  const headerString = xhr.getAllResponseHeaders();
  const lines = headerString.trim().split(/[\r\n]+/);
  
  lines.forEach(line => {
    const parts = line.split(': ');
    const header = parts.shift();
    const value = parts.join(': ');
    headers[header] = value;
  });
  
  return headers;
}

3. 错误处理

Axios提供了详细的错误信息,我们需要模拟这一行为:

function createError(xhr, config, message) {
  const error = new Error(message || `Request failed with status code ${xhr.status}`);
  error.config = config;
  error.code = xhr.status;
  error.request = xhr;
  error.response = {
    data: parseResponse(xhr),
    status: xhr.status,
    statusText: xhr.statusText,
    headers: parseHeaders(xhr)
  };
  return error;
}

4. 实现快捷方法

像Axios一样,我们可以为常用HTTP方法提供快捷方式:

const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];

httpMethods.forEach(method => {
  exports[method] = function(url, config = {}) {
    return request({
      ...config,
      method: method.toUpperCase(),
      url
    });
  };
});

5. 实现拦截器机制

拦截器是Axios的强大功能之一,我们可以模拟这一机制:

function createInterceptorManager() {
  const handlers = [];
  
  return {
    use: function(fulfilled, rejected) {
      handlers.push({
        fulfilled,
        rejected
      });
      return handlers.length - 1;
    },
    eject: function(id) {
      if (handlers[id]) {
        handlers[id] = null;
      }
    },
    forEach: function(fn) {
      handlers.forEach(h => {
        if (h !== null) {
          fn(h);
        }
      });
    }
  };
}

// 创建请求和响应拦截器
const interceptors = {
  request: createInterceptorManager(),
  response: createInterceptorManager()
};

6. 完整实现示例

结合以上各部分,我们可以创建一个简化的axios实现:

function createAxiosInstance() {
  const instance = function(config) {
    // 处理请求拦截器
    let requestConfig = {...config};
    const requestInterceptorChain = [];
    
    interceptors.request.forEach(interceptor => {
      requestInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });
    
    // 应用请求拦截器
    while (requestInterceptorChain.length) {
      const fulfilled = requestInterceptorChain.shift();
      const rejected = requestInterceptorChain.shift();
      try {
        requestConfig = fulfilled(requestConfig);
      } catch (error) {
        rejected(error);
        break;
      }
    }
    
    // 发送请求
    return request(requestConfig).then(response => {
      // 处理响应拦截器
      const responseInterceptorChain = [];
      
      interceptors.response.forEach(interceptor => {
        responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
      });
      
      let finalResponse = response;
      
      // 应用响应拦截器
      while (responseInterceptorChain.length) {
        const fulfilled = responseInterceptorChain.shift();
        const rejected = responseInterceptorChain.shift();
        try {
          finalResponse = fulfilled(finalResponse);
        } catch (error) {
          rejected(error);
          break;
        }
      }
      
      return finalResponse;
    });
  };
  
  // 添加快捷方法
  httpMethods.forEach(method => {
    instance[method] = function(url, config) {
      return this({
        ...config,
        method: method.toUpperCase(),
        url
      });
    };
  });
  
  // 添加拦截器
  instance.interceptors = interceptors;
  
  return instance;
}

const axios = createAxiosInstance();

总结

通过使用原生XMLHttpRequest实现Axios的核心功能,我们可以更深入地理解现代HTTP客户端库的工作原理。虽然我们的实现缺少Axios的一些高级功能(如取消请求、CSRF保护等),但它涵盖了大部分常用功能。

这种实现方式不仅有助于学习目的,还能在某些无法使用第三方库的环境中提供类似的API体验。理解底层原理有助于我们更好地使用和调试基于Axios的应用程序。

需要注意的是,在实际项目中,通常建议使用官方Axios库,因为它经过充分测试、有良好的社区支持,并且包含了许多我们未实现的边缘情况处理。

可以进扣扣裙(651706395),互相交流!

posted @ 2025-09-02 17:16  深圳蔓延科技有限公司  阅读(16)  评论(0)    收藏  举报