Ajax 技术与实现

理解 Ajax 原理,掌握 XMLHttpRequest 和 Fetch API 的使用

问题

什么是 Ajax?如何使用原生 JavaScript 实现 Ajax 请求?

解答

Ajax(Asynchronous JavaScript and XML)是一种在不刷新页面的情况下与服务器交换数据的技术。

XMLHttpRequest 方式

// 封装 XMLHttpRequest
function ajax(options) {
  const { method = 'GET', url, data = null, headers = {} } = options;
  
  return new Promise((resolve, reject) => {
    // 1. 创建 XMLHttpRequest 对象
    const xhr = new XMLHttpRequest();
    
    // 2. 初始化请求
    xhr.open(method, url, true);
    
    // 3. 设置请求头
    Object.keys(headers).forEach(key => {
      xhr.setRequestHeader(key, headers[key]);
    });
    
    // 4. 监听状态变化
    xhr.onreadystatechange = function() {
      if (xhr.readyState !== 4) return;
      
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.responseText));
      } else {
        reject(new Error(`Request failed with status ${xhr.status}`));
      }
    };
    
    // 5. 错误处理
    xhr.onerror = function() {
      reject(new Error('Network error'));
    };
    
    // 6. 超时处理
    xhr.timeout = 10000;
    xhr.ontimeout = function() {
      reject(new Error('Request timeout'));
    };
    
    // 7. 发送请求
    xhr.send(data ? JSON.stringify(data) : null);
  });
}

// 使用示例
ajax({
  method: 'GET',
  url: 'https://api.example.com/users'
}).then(data => {
  console.log(data);
}).catch(err => {
  console.error(err);
});

Fetch API 方式

// 封装 Fetch
async function request(url, options = {}) {
  const {
    method = 'GET',
    data = null,
    headers = {},
    timeout = 10000
  } = options;

  // 创建 AbortController 用于超时控制
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      method,
      headers: {
        'Content-Type': 'application/json',
        ...headers
      },
      body: data ? JSON.stringify(data) : null,
      signal: controller.signal
    });

    clearTimeout(timeoutId);

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error('Request timeout');
    }
    throw error;
  }
}

// GET 请求
const users = await request('https://api.example.com/users');

// POST 请求
const newUser = await request('https://api.example.com/users', {
  method: 'POST',
  data: { name: 'John', age: 25 }
});

XMLHttpRequest 的 readyState

状态说明
0UNSENT对象已创建,未调用 open
1OPENEDopen 已调用
2HEADERS_RECEIVED收到响应头
3LOADING正在接收响应体
4DONE请求完成

处理并发请求

// 并行请求
async function parallel(urls) {
  const promises = urls.map(url => request(url));
  return Promise.all(promises);
}

// 串行请求
async function serial(urls) {
  const results = [];
  for (const url of urls) {
    results.push(await request(url));
  }
  return results;
}

// 控制并发数量
async function limitConcurrency(urls, limit = 3) {
  const results = [];
  const executing = [];

  for (const url of urls) {
    const p = request(url).then(res => {
      executing.splice(executing.indexOf(p), 1);
      return res;
    });
    
    results.push(p);
    executing.push(p);

    if (executing.length >= limit) {
      await Promise.race(executing);
    }
  }

  return Promise.all(results);
}

关键点

  • XMLHttpRequest 是传统方式,通过 readyStatestatus 判断请求状态
  • Fetch API 是现代方式,基于 Promise,语法更简洁
  • Fetch 不会因 HTTP 错误状态码(如 404、500)而 reject,需要手动检查 response.ok
  • Fetch 原生不支持超时,需要配合 AbortController 实现
  • 跨域请求需要服务端配置 CORS 或使用代理