用 Promise 封装 AJAX 请求(从原理到落地)

5 阅读2分钟

在 Promise 出现之前,我们写 AJAX 是这样的:

// 传统回调写法(回调地狱预警)
const xhr = new XMLHttpRequest();
xhr.open("GET", "url1", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    // 第一个请求成功,再发第二个请求
    const xhr2 = new XMLHttpRequest();
    xhr2.open("GET", "url2", true);
    xhr2.onreadystatechange = function() {
      if (xhr2.readyState === 4 && xhr2.status === 200) {
        // 第二个请求成功,再发第三个...
      }
    };
    xhr2.send();
  }
};
xhr.send();

「AJAX 是异步操作(发请求→等响应→出结果),Promise 正好用 3 个状态对应这个流程:」

  1. 发送 AJAX 请求后 → Promise 处于「pending 状态」(等待中);
  2. AJAX 请求成功(状态码 200)→ Promise 转为「fulfilled 状态」,调用resolve传递数据;
  3. AJAX 请求失败(状态码非 200 / 网络错误)→ Promise 转为「rejected 状态」,调用reject传递错误。

用 Promise 的状态,记录 AJAX 的异步结果,后续通过.then()/.catch()获取结果,不用嵌套回调

// 封装函数:传入URL,返回Promise对象
function getJSON(url) {
  // 1. 创建Promise对象,包裹AJAX异步操作
  return new Promise(function(resolve, reject) {
    // 2. 初始化AJAX核心对象(XMLHttpRequest)
    const xhr = new XMLHttpRequest();

    // 3. 配置请求:方法(GET)、URL、异步(true)
    xhr.open("GET", url, true);

    // 4. 监听请求状态变化(核心:判断请求是否完成)
    xhr.onreadystatechange = function() {
      // readyState是AJAX的状态:4表示请求已完成(不管成功/失败)
      if (xhr.readyState !== 4) return; // 未完成则直接返回,不处理

      // 5. 请求完成后,判断响应状态码
      if (xhr.status === 200) {
        // 状态码200 = 成功 → 调用resolve,传递响应数据
        resolve(xhr.response); // response是JSON对象(后续配置)
      } else {
        // 状态码非200 = 失败 → 调用reject,传递错误信息
        reject(new Error(`请求失败:${xhr.statusText}`));
      }
    };

    // 6. 监听网络错误(比如断网、跨域)
    xhr.onerror = function() {
      reject(new Error("网络错误,请求失败"));
    };

    // 7. 配置响应格式:自动把服务器返回的字符串转成JSON对象
    xhr.responseType = "json";

    // 8. 设置请求头:告诉服务器“我要JSON格式的数据”
    xhr.setRequestHeader("Accept", "application/json");

    // 9. 发送请求(GET请求没有请求体,传null)
    xhr.send(null);
  });
}
// 示例:请求JSONPlaceholder的测试接口
getJSON("https://jsonplaceholder.typicode.com/todos/1")
  // 成功回调:拿到JSON数据
  .then((data) => {
    console.log("请求成功,数据:", data);
    // 实际开发:渲染数据到页面
    // document.getElementById("todo-title").innerText = data.title;
    return data.userId; // 传递数据给下一个then(链式调用)
  })
  // 链式调用:用第一个请求的结果发第二个请求
  .then((userId) => {
    // 用userId请求用户信息
    return getJSON(`https://jsonplaceholder.typicode.com/users/${userId}`);
  })
  .then((userData) => {
    console.log("用户信息:", userData);
  })
  // 捕获所有失败(第一个/第二个请求失败都走这里)
  .catch((err) => {
    console.error(err.message); // 统一处理错误
  })
  // 无论成功/失败,都会执行(比如关闭loading动画)
  .finally(() => {
    console.log("请求结束,关闭loading");
  });

基础版只支持 GET,实际开发中需要 POST(比如提交表单),我们扩展成通用版ajax函数:

// 通用版AJAX封装:支持GET/POST,可传请求体、请求头
function ajax(options) {
  // 默认参数:method默认GET,data默认null,headers默认空对象
  const {
    url,
    method = "GET",
    data = null,
    headers = {}
  } = options;

  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(method.toUpperCase(), url, true); // 方法转大写,避免大小写问题

    // 设置请求头(支持自定义,比如Token)
    Object.keys(headers).forEach((key) => {
      xhr.setRequestHeader(key, headers[key]);
    });

    // 监听状态变化
    xhr.onreadystatechange = function() {
      if (xhr.readyState !== 4) return;
      if (xhr.status >= 200 && xhr.status < 300) { // 2xx都是成功状态码
        resolve(xhr.response);
      } else {
        reject(new Error(`请求失败:${xhr.statusText}`));
      }
    };

    xhr.onerror = () => reject(new Error("网络错误"));
    xhr.responseType = "json"; // 仍支持JSON响应

    // 处理请求体:POST请求需要把data转成字符串
    const requestData = data ? JSON.stringify(data) : null;
    // 如果是POST,默认设置Content-Type(表单提交/JSON提交)
    if (method.toUpperCase() === "POST" && !headers["Content-Type"]) {
      xhr.setRequestHeader("Content-Type", "application/json");
    }

    xhr.send(requestData); // 发送请求体(POST用)
  });
}
// 示例:提交表单数据(POST请求)
ajax({
  url: "https://jsonplaceholder.typicode.com/posts",
  method: "POST",
  data: {
    title: "前端Promise实战",
    body: "用Promise封装AJAX真好用",
    userId: 1
  },
  headers: {
    // 自定义请求头(比如Token)
    // "Authorization": "Bearer your-token"
  }
})
.then((res) => console.log("提交成功:", res))
.catch((err) => console.error(err.message));