本文介绍如何使用 Async.Operation 封装 jQuery Ajax 请求,解决回调地狱问题。通过封装 GET/POST 方法统一返回 Operation 对象,并结合 Async.chain 实现有序异步队列。文章还展示了如何扩展 wait 方法处理请求间隔,以规避服务器防攻击策略,提升代码可读性与稳定性。
Async.Operation 封装 Ajax 请求
设计 Async.Operation 的核心初衷,在于消除 Ajax 调用中必须显式传递回调函数的繁琐。为此,第一步是将底层的 Ajax 请求封装为 Async.Operation 对象。本文示例基于 jQuery 库,但无论使用何种基础库,均可采用相同思路进行封装。
具体实现代码如下:
var Ajax = {};
Ajax.get = function(url, data) {
var operation = new Async.Operation();
$.get(url, data, function(result) { operation.yield(result); }, "json");
return operation;
};
Ajax.post = function(url, data) {
var operation = new Async.Operation();
$.post(url, data, function(result) { operation.yield(result); }, "json");
return operation;
}; 鉴于当前服务器 API 仅支持 GET 和 POST 且数据格式固定为 JSON,此处直接屏蔽了 jQuery 的其他 Ajax 选项,并强制指定数据类型为 JSON。在实际项目中,可采用类似方式将 Ajax 封装为若干仅返回 Async.Operation 的方法,将 jQuery 的底层选项封装在 Ajax 这一层内部,不再向业务层暴露这些细节。
调用 Ajax
业务逻辑:获取好友对象
完成 Ajax 封装后,即可专注于业务逻辑的开发。
假设存在一个 Friend 对象,其 get 方法用于获取单个好友信息,getAll 方法用于获取所有好友列表。对应的服务器 API 分别为 friend(返回单个好友 JSON)和 friendlist(返回好友名称列表 JSON)。
首先分析基础的 get 方法实现:
具体代码如下:
function get(name) {
return Ajax.get("/friend", "name=" + encodeURIComponent(name));
} 实现看似简单,前提是服务器返回的 JSON 结构需与好友对象结构完全一致。若两者结构存在异构,则需额外代码将 JSON 映射为对象:
具体代码如下:
function get(name) {
var operation = new Async.Operation()
Ajax.get("/friend", "name=" + encodeURIComponent(name))
.addCallback(function(json) {
operation.yield(createFriendFromJson(json));
});
return operation;
} Ajax 队列
异步队列控制与防攻击策略
接下来编写 getAll 方法。由于 friendlist 接口仅返回好友名称列表,获取列表后需逐一调用 get 方法获取具体对象。考虑到并发调用多个 friend 接口可能触发服务器防攻击策略(如暂时封禁),因此必须对 friend 接口的调用进行排队处理。
具体代码如下:
function getAll(){
var operation = new Async.Operation();
var friends = [];
var chain = Async.chain();
Ajax.get("/friendlist", "")
.addCallback(function(json) {
for (var i = 0; i < json.length; i++) {
chain.next(function() {
return get(json.shift())
.addCallback(function(friend) { friends.push(friend); });
});
}
chain
.next(function() { operation.yield(friends); })
.go();
})
return operation;
} 假设 friendlist 接口返回 JSON 数组,获取该数组后构造一个等长的异步调用队列。队列中每个调用的逻辑一致:取出数组首个好友名称,通过 get 方法获取对应对象,并将其推入结果数组。在队列末端追加一个调用,用于返回保存了所有好友对象的数组。
本例未利用调用队列传递上一函数结果的特性,但已充分展示队列用途:使多个底层为 Ajax 请求的异步操作按固定顺序阻塞式执行。
由于底层异步函数直接返回 Async.Operation,可直接将其传递给 next 方法;也可用匿名函数包装后传递,此时匿名函数内部仅需一个 return 语句。
延时函数
扩展 wait 方法处理请求间隔
上述例子使用队列旨在避免触发服务器防攻击策略,但有时仍需更精细的控制。例如,若服务器要求两个请求间至少间隔 500 毫秒,否则视为攻击,则需在队列中插入该时间间隔。
在 next 调用的匿名函数中手动加入 setTimeout 虽可行,但编写辅助函数以无缝集成 Async.Operation 更为优雅。
具体代码如下:
Async.wait = function(delay, context) {
var operation = new Async.Operation();
setTimeout(function() { operation.yield(context); }, delay);
return operation;
};
Async.Operation.prototype.wait = function(delay, context) {
this.next(function(context) { return Async.wait(delay, context); });
} 借助此辅助方法,可在 getAll 方法中轻松实现每个 Ajax 请求间间隔 500 毫秒。只需在 for 循环内增加对 wait 的调用即可。
具体代码如下:
for (var i = 0; i < json.length; i++) {
chain
.wait(500)
.next(function() {
return get(json.shift())
.addCallback(function(friend) { friends.push(friend); });
});
} 小结
通过上述简单示例,我们了解了 Async.Operation 的常见使用方式,以及如何在需要时扩展其功能。希望 Async.Operation 能有效提升 Ajax 应用的代码可读性。