You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

复刻jQuery的$.ajax().done():自定义JS框架链式调用实现难题

解决类jQuery链式Ajax的回调问题

看起来你已经走在正确的路上了,核心问题在于异步时机的把控和链式调用的实现逻辑。你之前的代码把ajax函数改成了返回Promise,但又想在Promise上调用原型链上的success方法,这就导致了矛盾——Promise对象并没有success方法,而且你的success方法在请求完成前就直接执行了回调,自然拿不到正确的响应。

下面是重构后的完整解决方案,完全符合你想要的ajax(url, type, data).success(...).error(...)链式调用风格:

重构后的代码

var ajax = function(url, method, data) {
  // 确保通过new关键字调用,支持直接ajax()的写法
  if (!(this instanceof ajax)) {
    return new ajax(url, method, data);
  }
  
  var self = this;
  // 存储成功/失败的回调函数队列
  this.successCallbacks = [];
  this.errorCallbacks = [];
  
  // 内部用Promise处理异步请求逻辑
  this.promise = new Promise(function(resolve, reject) {
    self.xhttp = new XMLHttpRequest();
    self.method = method.toUpperCase();
    self.url = url;
    self.data = data;

    // 处理GET请求的参数序列化
    var urlParams = '';
    if (self.method === 'GET' && data) {
      urlParams = serialize(data);
    }

    // 初始化请求
    self.xhttp.open(self.method, self.url + urlParams);

    // POST请求设置Content-Type
    if (self.method === 'POST') {
      self.xhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    }

    // 先绑定状态变化事件,再发送请求(关键!避免错过状态更新)
    self.xhttp.onreadystatechange = function() {
      if (this.readyState === 4) {
        if (this.status === 200) {
          // 请求成功,返回响应文本
          resolve(this.responseText);
        } else {
          // 请求失败,返回包含状态码的错误对象
          reject({
            status: this.status,
            statusText: this.statusText
          });
        }
      }
    };

    // 处理POST请求的发送数据
    var sendData = null;
    if (self.method === 'POST' && data) {
      sendData = serialize(data).slice(1); // 去掉序列化后开头的?
    }

    self.xhttp.send(sendData);
  });

  // 绑定Promise的回调到我们的回调队列
  this.promise.then(function(response) {
    self.successCallbacks.forEach(callback => callback(response));
  }).catch(function(error) {
    self.errorCallbacks.forEach(callback => callback(error));
  });

  // 返回实例本身,支持链式调用
  return this;
};

// 注册成功回调,返回实例保持链式
ajax.prototype.success = function(callback) {
  if (typeof callback === 'function') {
    this.successCallbacks.push(callback);
  }
  return this;
};

// 注册失败回调,返回实例保持链式
ajax.prototype.error = function(callback) {
  if (typeof callback === 'function') {
    this.errorCallbacks.push(callback);
  }
  return this;
};

// 你的序列化函数保持不变
var serialize = function(obj, prefix) {
  var str = [], p;
  for(p in obj) {
    if(obj.hasOwnProperty(p)) {
      var k = prefix ? prefix + "[" + p + "]" : p,
          v = obj[p];
      str.push((v !== null && typeof v === "object") ? 
        serialize(v, k) : 
        encodeURIComponent(k) + "=" + encodeURIComponent(v));
    }
  }
  return '?' + str.join("&");
};

关键改进点说明

  1. 链式调用实现:success和error方法都会返回实例本身(return this),所以你可以链式调用多个回调,甚至多次调用success注册多个成功回调。
  2. 异步时机修正:先绑定onreadystatechange事件,再调用send(),彻底避免了请求已经完成但事件还没绑定的问题。
  3. 回调队列管理:用数组存储所有注册的回调,当Promise完成时,遍历执行所有对应的回调,支持多个回调并存。
  4. 完善的错误处理:失败时返回包含状态码和状态文本的对象,方便你做更精细的错误处理。
  5. 支持POST请求:新增了POST请求的Content-Type设置和数据处理,让你的工具更通用。

调用示例

ajax('https://your-api-url.com', 'GET', { 
  'Testval': 'testvalue', 
  'field': 'value' 
})
.success(function(response) {
  console.log("请求成功!响应数据:", response);
  // 这里可以按需处理响应,比如转成JSON
  // const data = JSON.parse(response);
})
.error(function(error) {
  console.error("请求失败:", error.status, error.statusText);
});

解决你之前的痛点

  • 不再出现response为false的问题:只有当请求完成并返回200状态时,才会执行success回调。
  • 避免Promise报错:内部已经处理了Promise的reject逻辑,不会出现未捕获的Promise错误。
  • 完全符合你想要的链式调用语法,不需要依赖原生Promise的then/catch。

内容的提问来源于stack exchange,提问作者NoobishPro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:43:49