复刻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("&"); };
关键改进点说明
- 链式调用实现:
success和error方法都会返回实例本身(return this),所以你可以链式调用多个回调,甚至多次调用success注册多个成功回调。 - 异步时机修正:先绑定
onreadystatechange事件,再调用send(),彻底避免了请求已经完成但事件还没绑定的问题。 - 回调队列管理:用数组存储所有注册的回调,当Promise完成时,遍历执行所有对应的回调,支持多个回调并存。
- 完善的错误处理:失败时返回包含状态码和状态文本的对象,方便你做更精细的错误处理。
- 支持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
相关产品推荐
相关产品推荐

