0-1的Ajax请求

Ajax请求实现与封装

基础原生Ajax请求

// 原生XMLHttpRequest实现
function ajaxRequest(options) {
  // 默认参数
  const defaultOptions = {
    url: '',
    method: 'GET',
    data: null,
    headers: {
      'Content-Type': 'application/json'
    },
    async: true,
    timeout: 10000,
    success: null,
    error: null
  };
  
  // 合并选项
  options = Object.assign({}, defaultOptions, options);
  
  // 创建xhr对象
  const xhr = new XMLHttpRequest();
  
  // 设置超时
  xhr.timeout = options.timeout;
  
  // 监听状态变化
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      if (xhr.status >= 200 && xhr.status < 300) {
        let response;
        try {
          response = JSON.parse(xhr.responseText);
        } catch (e) {
          response = xhr.responseText;
        }
        options.success && options.success(response);
      } else {
        options.error && options.error(xhr.status, xhr.statusText);
      }
    }
  };
  
  // 错误处理
  xhr.onerror = function() {
    options.error && options.error(xhr.status, xhr.statusText);
  };
  
  // 超时处理
  xhr.ontimeout = function() {
    options.error && options.error('timeout', '请求超时');
  };
  
  // 处理GET请求参数
  if (options.method.toUpperCase() === 'GET' && options.data) {
    const queryString = typeof options.data === 'object' 
      ? Object.keys(options.data).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(options.data[key])}`).join('&')
      : options.data;
    options.url += (options.url.includes('?') ? '&' : '?') + queryString;
    options.data = null;
  }
  
  // 打开连接
  xhr.open(options.method, options.url, options.async);
  
  // 设置请求头
  Object.keys(options.headers).forEach(key => {
    xhr.setRequestHeader(key, options.headers[key]);
  });
  
  // 发送请求
  if (options.method.toUpperCase() === 'POST' || options.method.toUpperCase() === 'PUT') {
    const data = options.headers['Content-Type'].includes('application/json')
      ? JSON.stringify(options.data)
      : options.data;
    xhr.send(data);
  } else {
    xhr.send();
  }
  
  // 返回xhr对象,方便外部控制
  return xhr;
}

实际使用示例

// GET请求示例
ajaxRequest({
  url: 'https://api.example.com/users',
  method: 'GET',
  success: function(data) {
    console.log('获取用户列表成功:', data);
  },
  error: function(status, message) {
    console.error('获取失败:', status, message);
  }
});

// POST请求示例
ajaxRequest({
  url: 'https://api.example.com/users',
  method: 'POST',
  data: {
    name: '张三',
    age: 30,
    email: '[email protected]'
  },
  success: function(response) {
    console.log('创建用户成功:', response);
  },
  error: function(status, message) {
    console.error('创建失败:', status, message);
  }
});

Promise版本封装

// 基于Promise的Ajax封装
function ajax(options) {
  return new Promise((resolve, reject) => {
    // 默认配置
    const defaultOptions = {
      url: '',
      method: 'GET',
      data: null,
      headers: {
        'Content-Type': 'application/json'
      },
      timeout: 10000,
      withCredentials: false  // 是否携带凭证
    };
    
    // 合并配置
    options = {...defaultOptions, ...options};
    
    // 创建xhr对象
    const xhr = new XMLHttpRequest();
    
    // 设置超时
    xhr.timeout = options.timeout;
    
    // 设置是否携带凭证
    xhr.withCredentials = options.withCredentials;
    
    // 监听加载完成
    xhr.onload = function() {
      if (xhr.status >= 200 && xhr.status < 300) {
        let response;
        try {
          response = JSON.parse(xhr.responseText);
        } catch (e) {
          response = xhr.responseText;
        }
        resolve(response);
      } else {
        reject({
          status: xhr.status,
          statusText: xhr.statusText,
          response: xhr.responseText
        });
      }
    };
    
    // 监听错误
    xhr.onerror = function() {
      reject({
        status: xhr.status,
        statusText: '网络错误',
        response: null
      });
    };
    
    // 监听超时
    xhr.ontimeout = function() {
      reject({
        status: 'timeout',
        statusText: '请求超时',
        response: null
      });
    };
    
    // 处理GET请求参数
    if (options.method.toUpperCase() === 'GET' && options.data) {
      const queryString = typeof options.data === 'object' 
        ? Object.keys(options.data).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(options.data[key])}`).join('&')
        : options.data;
      options.url += (options.url.includes('?') ? '&' : '?') + queryString;
      options.data = null;
    }
    
    // 打开连接
    xhr.open(options.method, options.url, true);
    
    // 设置请求头
    Object.keys(options.headers).forEach(key => {
      xhr.setRequestHeader(key, options.headers[key]);
    });
    
    // 发送请求
    if (options.method.toUpperCase() === 'POST' || options.method.toUpperCase() === 'PUT') {
      const contentType = options.headers['Content-Type'];
      let data = options.data;
      
      if (contentType && contentType.includes('application/json') && typeof data === 'object') {
        data = JSON.stringify(data);
      } else if (contentType && contentType.includes('application/x-www-form-urlencoded') && typeof data === 'object') {
        data = Object.keys(data).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`).join('&');
      }
      
      xhr.send(data);
    } else {
      xhr.send();
    }
  });
}

实际工作中的Ajax封装经验

在实际工作中,我通常会封装一个更完善的HTTP请求库,包含以下特性:

  1. 拦截器机制:支持请求前和响应后的统一处理
// 拦截器实现
const httpClient = {
  interceptors: {
    request: [],
    response: []
  },
  
  // 添加请求拦截器
  addRequestInterceptor(fulfilled, rejected) {
    this.interceptors.request.push({ fulfilled, rejected });
  },
  
  // 添加响应拦截器
  addResponseInterceptor(fulfilled, rejected) {
    this.interceptors.response.push({ fulfilled, rejected });
  },
  
  // 执行请求
  request(config) {
    // 应用请求拦截器
    let requestConfig = {...config};
    this.interceptors.request.forEach(interceptor => {
      if (interceptor.fulfilled) {
        requestConfig = interceptor.fulfilled(requestConfig);
      }
    });
    
    // 发起请求
    return ajax(requestConfig).then(
      response => {
        // 应用响应拦截器
        let responseResult = response;
        this.interceptors.response.forEach(interceptor => {
          if (interceptor.fulfilled) {
            responseResult = interceptor.fulfilled(responseResult);
          }
        });
        return responseResult;
      },
      error => {
        // 应用错误拦截器
        let errorResult = error;
        this.interceptors.response.forEach(interceptor => {
          if (interceptor.rejected) {
            errorResult = interceptor.rejected(errorResult);
          }
        });
        return Promise.reject(errorResult);
      }
    );
  },
  
  // 便捷方法
  get(url, params, config = {}) {
    return this.request({
      ...config,
      method: 'GET',
      url,
      data: params
    });
  },
  
  post(url, data, config = {}) {
    return this.request({
      ...config,
      method: 'POST',
      url,
      data
    });
  }
  
  // 其他方法...
};
  1. 统一错误处理:处理常见HTTP状态码和网络错误
// 错误处理示例
httpClient.addResponseInterceptor(
  response => response,
  error => {
    if (error.status === 401) {
      // 未授权,跳转登录
      location.href = '/login';
    } else if (error.status === 403) {
      // 权限不足
      showNotification('您没有权限执行此操作');
    } else if (error.status === 500) {
      // 服务器错误
      showNotification('服务器错误,请稍后再试');
    } else if (error.status === 'timeout') {
      // 超时处理
      showNotification('请求超时,请检查网络');
    }
    return Promise.reject(error);
  }
);
  1. 请求取消功能:支持取消正在进行的请求
// 请求取消功能
function createCancelToken() {
  let cancel;
  const token = new Promise((resolve) => {
    cancel = resolve;
  });
  token.cancel = cancel;
  return token;
}

// 使用方式
const cancelToken = createCancelToken();
httpClient.get('/api/data', null, { cancelToken })
  .catch(error => {
    if (error.isCanceled) {
      console.log('请求已取消');
    }
  });

// 取消请求
cancelToken.cancel('用户取消');
  1. 请求缓存:对相同GET请求进行缓存
// 简单请求缓存实现
const cacheMap = new Map();

httpClient.addRequestInterceptor(config => {
  // 只对GET请求进行缓存
  if (config.method === 'GET' && config.useCache) {
    const cacheKey = `${config.url}${JSON.stringify(config.data || {})}`;
    const cachedResponse = cacheMap.get(cacheKey);
    
    if (cachedResponse && Date.now() - cachedResponse.timestamp < config.cacheTime) {
      // 使用缓存数据
      config.useCache = cachedResponse.data;
    } else {
      // 标记需要缓存
      config._cacheKey = cacheKey;
    }
  }
  return config;
});

httpClient.addResponseInterceptor(response => {
  const config = response.config;
  // 如果请求需要缓存
  if (config.method === 'GET' && config.useCache && config._cacheKey) {
    cacheMap.set(config._cacheKey, {
      data: response,
      timestamp: Date.now()
    });
  }
  return response;
});
  1. 自动重试机制:网络错误时自动重试
// 请求重试功能
function requestWithRetry(config, maxRetries = 3, delay = 1000) {
  return httpClient.request(config).catch(error => {
    // 只在网络错误或服务器错误时重试
    if ((error.status === 0 || error.status >= 500) && maxRetries > 0) {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(requestWithRetry(config, maxRetries - 1, delay * 2));
        }, delay);
      });
    }
    return Promise.reject(error);
  });
}

在实际工作中,通常不会完全从零开始封装Ajax,而是基于成熟的库如Axios进行二次封装,增加项目所需的特定功能。

通过封装Ajax,能够:

  1. 统一处理请求/响应格式
  2. 集中处理认证信息
  3. 简化错误处理
  4. 提高代码复用性
  5. 方便进行全局配置管理

你可能感兴趣的:(ajax,okhttp,前端)