手写实现Ajax

从零实现一个支持Promise的Ajax函数,掌握XMLHttpRequest的用法

问题

Ajax(Asynchronous JavaScript and XML)是前端与后端进行异步数据交互的技术。这道题要求我们手写实现一个Ajax函数,需要:

  1. 封装原生的XMLHttpRequest对象
  2. 支持GET、POST等常见HTTP方法
  3. 支持Promise风格的异步调用
  4. 能够设置请求头、超时时间等配置
  5. 正确处理响应数据和错误情况

解答

/**
 * 手写实现Ajax
 * @param {Object} options 配置对象
 * @param {string} options.url 请求地址
 * @param {string} options.method 请求方法,默认GET
 * @param {Object} options.data 请求数据
 * @param {Object} options.headers 请求头
 * @param {number} options.timeout 超时时间(毫秒)
 * @param {string} options.responseType 响应类型
 * @returns {Promise}
 */
function ajax(options) {
  return new Promise((resolve, reject) => {
    // 默认配置
    const {
      url,
      method = 'GET',
      data = null,
      headers = {},
      timeout = 0,
      responseType = 'json'
    } = options;

    // 创建XMLHttpRequest对象
    const xhr = new XMLHttpRequest();

    // 处理GET请求的参数拼接
    let requestUrl = url;
    if (method.toUpperCase() === 'GET' && data) {
      const params = new URLSearchParams(data).toString();
      requestUrl = `${url}?${params}`;
    }

    // 初始化请求
    xhr.open(method, requestUrl, true);

    // 设置响应类型
    xhr.responseType = responseType;

    // 设置超时时间
    if (timeout > 0) {
      xhr.timeout = timeout;
    }

    // 设置请求头
    Object.keys(headers).forEach(key => {
      xhr.setRequestHeader(key, headers[key]);
    });

    // 如果是POST请求且没有设置Content-Type,默认设置为json
    if (method.toUpperCase() === 'POST' && !headers['Content-Type']) {
      xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
    }

    // 监听请求成功
    xhr.onload = function() {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve({
          data: xhr.response,
          status: xhr.status,
          statusText: xhr.statusText,
          headers: xhr.getAllResponseHeaders()
        });
      } else {
        reject(new Error(`请求失败: ${xhr.status} ${xhr.statusText}`));
      }
    };

    // 监听请求错误
    xhr.onerror = function() {
      reject(new Error('网络请求失败'));
    };

    // 监听超时
    xhr.ontimeout = function() {
      reject(new Error('请求超时'));
    };

    // 监听请求中止
    xhr.onabort = function() {
      reject(new Error('请求已中止'));
    };

    // 发送请求
    if (method.toUpperCase() === 'POST' && data) {
      // POST请求发送JSON数据
      xhr.send(JSON.stringify(data));
    } else {
      xhr.send();
    }
  });
}

使用示例

// 示例1: GET请求
ajax({
  url: 'https://api.example.com/users',
  method: 'GET',
  data: { page: 1, limit: 10 }
})
  .then(response => {
    console.log('请求成功:', response.data);
  })
  .catch(error => {
    console.error('请求失败:', error.message);
  });

// 示例2: POST请求
ajax({
  url: 'https://api.example.com/login',
  method: 'POST',
  data: {
    username: 'admin',
    password: '123456'
  },
  headers: {
    'Authorization': 'Bearer token123'
  },
  timeout: 5000
})
  .then(response => {
    console.log('登录成功:', response.data);
  })
  .catch(error => {
    console.error('登录失败:', error.message);
  });

// 示例3: 使用async/await
async function fetchUserData() {
  try {
    const response = await ajax({
      url: 'https://api.example.com/user/1',
      method: 'GET',
      responseType: 'json'
    });
    console.log('用户数据:', response.data);
  } catch (error) {
    console.error('获取失败:', error.message);
  }
}

fetchUserData();

关键点

  • Promise封装:使用Promise包装XMLHttpRequest,使其支持现代异步编程风格,便于使用async/await

  • 参数处理:GET请求需要将data对象转换为URL查询参数,POST请求需要将data转换为JSON字符串

  • 请求初始化:使用xhr.open(method, url, true)初始化请求,第三个参数true表示异步请求

  • 状态监听:需要监听多个事件(onload、onerror、ontimeout、onabort)来完整处理各种情况

  • 状态码判断:HTTP状态码在200-299之间才算成功,其他状态码应该reject

  • 请求头设置:必须在open()之后、send()之前调用setRequestHeader()设置请求头

  • 响应类型:通过responseType属性设置响应数据类型(json、text、blob等),自动解析响应数据

  • 超时处理:设置timeout属性并监听ontimeout事件,避免请求长时间挂起