【HTML5】【AJAX的几种封装方法详解】

【HTML5】【AJAX的几种封装方法详解】

AJAX (Asynchronous JavaScript and XML) 封装是为了简化重复的异步请求代码,提高开发效率和代码复用性。下面我将介绍几种常见的 AJAX 封装方式。

方法1. 基于原生 XMLHttpRequest 的封装

XMLHttpRequest。其主要特点如下:

  1. 实现动态不刷新,通过异步⽅式,提升⽤户体验,优化了浏览器和服务器之间的传输。

  2. 把⼀部分原本由服务器负担的⼯作转移到客户端,利⽤客户端闲置的资源进⾏处理,减轻服务器和带宽的负担,节约空间和成本。

  3. ⽆刷新更新⻚⾯,⽤户不⽤再像以前⼀样在服务器处理数据时,只能在死板的⽩屏前焦急的等待。AJAX使⽤XMLHttpRequest对象发送请求并得到服务器响应,在不需要重新载⼊整个⻚⾯的情况下,就可以通过DOM及时将更新的内容显示在⻚⾯上。

    /**

    • 基于原生XHR的AJAX封装
    • @param {Object} options 配置对象
    • @param {string} options.url 请求地址
    • @param {string} [options.method='GET'] 请求方法
    • @param {Object} [options.data=null] 请求数据
    • @param {Object} [options.headers={}] 请求头
    • @param {function} [options.success] 成功回调
    • @param {function} [options.error] 失败回调
      */
      function ajax(options) {
      const xhr = new XMLHttpRequest();
      const method = options.method || 'GET';
      let url = options.url;
      let data = options.data || null;

    // 处理GET请求的查询参数
    if (method === 'GET' && data) {
    const params = new URLSearchParams();
    for (const key in data) {
    params.append(key, data[key]);
    }
    url += '?' + params.toString();
    data = null;
    }

    xhr.open(method, url, true);

    // 设置请求头
    if (options.headers) {
    for (const key in options.headers) {
    xhr.setRequestHeader(key, options.headers[key]);
    }
    }

    xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
    if (xhr.status >= 200 && xhr.status < 300) {
    let response = xhr.responseText;
    try {
    response = JSON.parse(response);
    } catch (e) {}
    options.success && options.success(response);
    } else {
    options.error && options.error(xhr.status, xhr.statusText);
    }
    }
    };

    xhr.onerror = function() {
    options.error && options.error(-1, 'Network Error');
    };

    // 发送请求
    if (data && typeof data === 'object') {
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.send(JSON.stringify(data));
    } else {
    xhr.send(data);
    }
    }

    // 使用示例
    ajax({
    url: '/api/user',
    method: 'POST',
    data: { name: 'John', age: 30 },
    headers: {
    'Authorization': 'Bearer token123'
    },
    success: function(response) {
    console.log('Success:', response);
    },
    error: function(status, statusText) {
    console.error('Error:', status, statusText);
    }
    });

方法2. 基于 Fetch API 的封装

复制代码
/**
 * 基于Fetch API的AJAX封装
 * @param {string} url 请求地址
 * @param {Object} [options={}] 请求配置
 * @returns {Promise} 返回Promise对象
 */
function fetchAjax(url, options = {}) {
  const defaultOptions = {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json'
    },
    credentials: 'same-origin', // 携带cookie
    ...options
  };

  // 处理GET请求的查询参数
  if (defaultOptions.method === 'GET' && defaultOptions.body) {
    const params = new URLSearchParams();
    for (const key in defaultOptions.body) {
      params.append(key, defaultOptions.body[key]);
    }
    url += '?' + params.toString();
    delete defaultOptions.body;
  }

  // 处理非GET请求的body数据
  if (defaultOptions.body && typeof defaultOptions.body === 'object') {
    defaultOptions.body = JSON.stringify(defaultOptions.body);
  }

  return fetch(url, defaultOptions)
    .then(async response => {
      const data = await response.json().catch(() => ({}));
      if (!response.ok) {
        const error = new Error(response.statusText);
        error.response = response;
        error.data = data;
        throw error;
      }
      return data;
    });
}

// 使用示例
fetchAjax('/api/user', {
  method: 'POST',
  body: { name: 'John', age: 30 },
  headers: {
    'Authorization': 'Bearer token123'
  }
})
.then(data => console.log('Success:', data))
.catch(err => console.error('Error:', err));

方法 3. 基于 Axios 风格的封装

复制代码
class Ajax {
  constructor(baseURL = '', timeout = 10000) {
    this.baseURL = baseURL;
    this.timeout = timeout;
    this.interceptors = {
      request: [],
      response: []
    };
  }

  request(config) {
    // 处理请求拦截器
    let chain = [this._dispatchRequest, undefined];
    this.interceptors.request.forEach(interceptor => {
      chain.unshift(interceptor.fulfilled, interceptor.rejected);
    });
    this.interceptors.response.forEach(interceptor => {
      chain.push(interceptor.fulfilled, interceptor.rejected);
    });

    let promise = Promise.resolve(config);
    while (chain.length) {
      promise = promise.then(chain.shift(), chain.shift());
    }
    return promise;
  }

  _dispatchRequest(config) {
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      let url = config.baseURL ? config.baseURL + config.url : config.url;
      let data = config.data;

      // 处理GET请求参数
      if (config.method === 'GET' && data) {
        const params = new URLSearchParams();
        for (const key in data) {
          params.append(key, data[key]);
        }
        url += '?' + params.toString();
        data = null;
      }

      xhr.timeout = config.timeout || 10000;
      xhr.open(config.method, url, true);

      // 设置请求头
      if (config.headers) {
        for (const key in config.headers) {
          xhr.setRequestHeader(key, config.headers[key]);
        }
      }

      xhr.onload = function() {
        if (xhr.status >= 200 && xhr.status < 300) {
          let response = xhr.responseText;
          try {
            response = JSON.parse(response);
          } catch (e) {}
          resolve({
            data: response,
            status: xhr.status,
            statusText: xhr.statusText,
            headers: xhr.getAllResponseHeaders()
          });
        } else {
          reject(new Error(`Request failed with status code ${xhr.status}`));
        }
      };

      xhr.onerror = function() {
        reject(new Error('Network Error'));
      };

      xhr.ontimeout = function() {
        reject(new Error('Timeout'));
      };

      // 发送请求
      if (data && typeof data === 'object') {
        xhr.setRequestHeader('Content-Type', 'application/json');
        xhr.send(JSON.stringify(data));
      } else {
        xhr.send(data);
      }
    });
  }

  get(url, config = {}) {
    return this.request({
      ...config,
      method: 'GET',
      url
    });
  }

  post(url, data, config = {}) {
    return this.request({
      ...config,
      method: 'POST',
      url,
      data
    });
  }

  // 添加拦截器
  useRequestInterceptor(fulfilled, rejected) {
    this.interceptors.request.push({ fulfilled, rejected });
    return this.interceptors.request.length - 1;
  }

  useResponseInterceptor(fulfilled, rejected) {
    this.interceptors.response.push({ fulfilled, rejected });
    return this.interceptors.response.length - 1;
  }

  // 移除拦截器
  ejectRequestInterceptor(id) {
    if (this.interceptors.request[id]) {
      this.interceptors.request.splice(id, 1);
    }
  }

  ejectResponseInterceptor(id) {
    if (this.interceptors.response[id]) {
      this.interceptors.response.splice(id, 1);
    }
  }
}

// 使用示例
const api = new Ajax('https://api.example.com');

// 添加请求拦截器
api.useRequestInterceptor(config => {
  config.headers = config.headers || {};
  config.headers['Authorization'] = 'Bearer token123';
  return config;
});

// 添加响应拦截器
api.useResponseInterceptor(response => {
  console.log('Response:', response);
  return response.data;
}, error => {
  console.error('Error:', error);
  return Promise.reject(error);
});

// 发起请求
api.get('/user/123')
  .then(data => console.log('User data:', data))
  .catch(err => console.error('Error:', err));

api.post('/user', { name: 'John', age: 30 })
  .then(data => console.log('Created user:', data))
  .catch(err => console.error('Error:', err));

4. 封装要点总结

统一接口:提供一致的调用方式,如get(), post()等方法

参数处理:

GET请求自动拼接查询参数

POST请求自动处理Content-Type

拦截器机制:支持请求/响应拦截

错误处理:统一错误处理逻辑

Promise支持:返回Promise便于链式调用

超时处理:设置合理的请求超时时间

扩展性:支持自定义配置和拦截器

5. 实际项目中的增强功能

1.自动重试机制:

复制代码
function withRetry(fn, retries = 3, delay = 1000) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      function attempt(retryCount) {
        fn(...args)
          .then(resolve)
          .catch(err => {
            if (retryCount < retries) {
              setTimeout(() => attempt(retryCount + 1), delay);
            } else {
              reject(err);
            }
          });
      }
      attempt(0);
    });
  };
}

// 使用示例
const ajaxWithRetry = withRetry(ajax, 3, 1000);

2.请求取消功能:

复制代码
function createCancelToken() {
  let cancel;
  const token = new Promise((resolve, reject) => {
    cancel = reject;
  });
  return { token, cancel };
}

// 在请求中检查取消token
function ajaxWithCancel(options) {
  const { token, cancel } = createCancelToken();
  const xhr = new XMLHttpRequest();
  
  const promise = new Promise((resolve, reject) => {
    // ...正常请求逻辑
    
    // 检查取消
    token.catch(err => {
      xhr.abort();
      reject(err);
    });
  });
  
  return { promise, cancel };
}

3.请求缓存:

复制代码
const cache = new Map();

function cachedAjax(options) {
  const cacheKey = JSON.stringify(options);
  
  if (cache.has(cacheKey)) {
    return Promise.resolve(cache.get(cacheKey));
  }
  
  return ajax(options).then(response => {
    cache.set(cacheKey, response);
    return response;
  });
}

根据项目需求选择合适的封装方式,小型项目可使用简单封装,大型项目建议使用成熟的库如Axios。

相关推荐
酒鼎6 小时前
学习笔记(3)HTML5新特性(第2章)
笔记·学习·html5
人良爱编程1 天前
Hugo的Stack主题配置记录03-背景虚化-导航栏-Apache ECharts创建地图
前端·javascript·apache·echarts·css3·html5
WooaiJava1 天前
AI 智能助手项目面试技术要点总结(前端部分)
javascript·大模型·html5
夜郎king1 天前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
夏幻灵1 天前
HTML5里最常用的十大标签
前端·html·html5
vistaup1 天前
OKHTTP 默认构建包含 android 4.4 的TLS 1.2 以及设备时间不对兼容
android·okhttp
咔咔一顿操作2 天前
轻量无依赖!autoviwe 页面自适应组件实战:从安装到源码深度解析
javascript·arcgis·npm·css3·html5
酒鼎3 天前
学习笔记(4)HTML5新特性(第3章)- WebSocket
笔记·学习·html5
七刀4 天前
基金实时估值系统
html5
a1117764 天前
医院挂号预约系统(开源 Fastapi+vue2)
前端·vue.js·python·html5·fastapi