共计 5821 个字符,预计需要花费 15 分钟才能阅读完成。
简要说明
前面我写了一篇《封装 jquery ajax 及 api 文件》文档,主要用来说明我们在项目中通常会对 jquery 的 ajax 方法进行进一步的封装处理,便于我们在业务代码中使用。从那篇文档中我们可以了解到如何封装 ajax 方法、如何设计 API 文件,以及如何在业务代码中调用 API 接口。
这篇文档我们主要对封装的 ajax 方法进行一个简要说明。这里的封装主要是将 $.ajax() 返回的 jqXHR 对象,通过调用 jQuery.Deferred() 方法创建成一个可链式调用的工具对象(其实 jqXHR 本身就是 Deferred 对象,本来就可以进行链式调用,后面会对此进行说明)。这样我们就可以使用下面这种形式进行链式调用:
// 链式调用
absService.getAbsListData(conditionObj)
.done(function (result) {
var data = ts.handleData(result);
ts.render(data, columnChange);
})
.fail(function (err, jqXHR) {
Util.hideLoading(‘#abs-all-container’);
});
同时,通过一些处理,也允许调用者使用如下形式进行调用:
// 传入成功回调、失败回调
absService.getAbsListData(conditionObj, function (result) {
var data = ts.handleData(result);
ts.render(data, columnChange);
}, function (err, jqXHR) {
Util.hideLoading(‘#abs-all-container’);
});
实际上,从 jQuery 1.5 开始,$.ajax() 返回的 jqXHR 对象就已经实现了 Promise 接口, 使它拥有了 Promise 的所有属性,方法和行为。也就是说 $.ajax() 返回的 jqXHR 对象本身就可以使用如下形式的调用:
$.ajax(options)
.done(function (result, textStatus, jqXHR) {
if (requestId === requestIdentifier[serviceName]) {
ajaxRequest.ajax.handleResponse(result, $dfd, jqXHR, userOptions, serviceName, requestId);
}
})
.fail(function (jqXHR, textStatus, errorThrown) {
if (requestId === requestIdentifier[serviceName]) {
//jqXHR.status
$dfd.reject.apply(this, arguments);
userOptions.error.apply(this, arguments);
}
});
既然已经可以使用这种方式使用 jqXHR 对象,那为什么不直接将 jqXHR 对象返回,然后在业务代码中直接使用 jqXHR.done() 和 jqXHR.fail() 方法呢?为什么还要进一步将其包装成一个新的 Deferred 对象呢?
答:是为了在成功和失败回调调用之前做一些统一的处理,比如对接口返回的数据进行判断、对返回的 JSON 字符串进行解析、在请求失败时输出方法名和错误信息等,我们把这些每次接口调用都会进行的操作进行统一处理,实现代码复用,这样业务代码中就只需要关注拿到数据后的处理逻辑即可。
这些统一进行的操作,我们放在 $.ajax().done() 和 $.ajax().fail() 方法中去执行,然后将 $.ajax() 返回的 jqXHR 对象包装成新的 Deferred 对象,这样就可以在业务代码中通过链式调用做进一步的处理,比如刷新表格内容、更新页面动态等等。
代码解读
/**
* 封装 jquery ajax
* 例如:
* ajaxRequest.ajax.triggerService(
* ‘apiCommand’, [命令数据] )
* .then(successCallback, failureCallback);
* );
*/
var JSON2 = require(‘LibsDir/json2’);
var URL = ‘../AjaxHandler.aspx?r=’;
// 用来记录每次请求的唯一标识, 键值对形式, 形如:
// var requestIdentifier = {
// SearchABSList: ‘b80a3876-9bca-40d1-b2cd-0daa799591e7’,
// SetABSUserColumns: ‘cafe3f01-2ee2-41f0-aeca-d630429f89f4’,
// };
var requestIdentifier = {};
// 唯一标识生成方法
function generateGUID() {
var d = new Date().getTime();
if (typeof performance !== ‘undefined’ && typeof performance.now === ‘function’) {
d += performance.now();
}
return ‘xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx’.replace(/[xy]/g, function (c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === ‘x’ ? r : (r & 0x3 | 0x8)).toString(16);
});
}
// 模块主体
var ajaxRequest = ajaxRequest || {};
(function ($) {
if (!$) {
throw ‘jquery 获取失败!’;
}
ajaxRequest.json = JSON2;
/**
* 触发请求, 返回包装成 Deferred 对象的 jqXHR
* @param {object} userOptions 参数对象(包含方法名、参数对象、成功回调、失败回调)
* @param {string} serviceName 方法名
* @param {string} requestId 请求的唯一标识
* @returns object
*/
ajaxRequest.ajax = function (userOptions, serviceName, requestId) {
userOptions = userOptions || {};
// 将参数对象与 ajax 的默认项组成一个新的对象, 作为 jquery ajax 方法的配置项
var options = $.extend({}, ajaxRequest.ajax.defaultOpts, userOptions);
// 将 jquery ajax 方法默认的 success 回调和 error 回调置为 undefined
// .done()方法取代了的过时的 jqXHR.success()方法
// .fail()方法取代了的过时的.error()方法
options.success = undefined;
options.error = undefined;
// 将 jqXHR 包装成新的 Deferred 对象返回
// jQuery.Deferred([beforeStart]) 一个工厂函数, 这个函数返回一个链式实用对象
// beforeStart – 一个构造函数返回之前调用的函数
return $.Deferred(function ($dfd) {
$.ajax(options)
.done(function (result, textStatus, jqXHR) {
if (requestId === requestIdentifier[serviceName]) {
// 比对请求 id, 保证返回结果的正确性 (重复请求有可能会带来返回结果不可靠的问题)
ajaxRequest.ajax.handleResponse(result, $dfd, jqXHR, userOptions, serviceName, requestId);
}
})
.fail(function (jqXHR, textStatus, errorThrown) {
if (requestId === requestIdentifier[serviceName]) {
// jqXHR.status
$dfd.reject.apply(this, arguments);
userOptions.error.apply(this, arguments);
}
});
});
};
$.extend(ajaxRequest.ajax, {
// $.ajax() 的默认设置
defaultOpts: {
// url: ‘../AjaxSecureHandler.aspx,
dataType: ‘json’,
type: ‘POST’,
contentType: ‘application/x-www-form-urlencoded; charset=UTF-8’
},
// 在业务代码的回调执行之前, 做一些统一处理, 实现代码复用
// $dfd.resolve() 和 $dfd.reject() 的执行, 会使得使用 .then()/.done() 中的回调方法得到执行
// userOptions.success() 和 userOptions.error() 是调用用户以参数形式传入的成功回调函数和失败回调函数
// 一般来说, 上述两种方式只会有一个真正起到作用, 这取决于用户调用的方式:
// 如果是使用 $.ajax().done().fail() 这种方式调用, 那么 $dfd.resolve()/$dfd.reject() 会起到作用
// 此时 userOptions.success/userOptions.error 都是 undefined, 自然不会得到执行, 反之亦然
handleResponse: function (result, $dfd, jqXHR, userOptions, serviceName, requestId) {
if (!result) {
$dfd && $dfd.reject(jqXHR, ‘error response format!’);
userOptions.error(jqXHR, ‘error response format!’);
return;
}
// 服务器已经错误
if (result.ErrorCode != ‘200’) {
$dfd && $dfd.reject(jqXHR, result.ErrorMessage);
userOptions.error(jqXHR, result);
return;
}
if (result.Data) {
// 将大于 2^53 的数字 (16 位以上) 包裹双引号, 避免溢出
var jsonStr = result.Data.replace(/(:\s*)(\d{16,})(\s*,|\s*})/g, ‘$1″$2″$3’);
var resultData = ajaxRequest.json.parse(jsonStr);
// $dfd.resolve() 执行之后, 业务代码中的回调会执行, 即.then()/.done()方法会得到执行
$dfd.resolve(resultData);
// 如果是使用传统的传入成功回调函数的形式进行调用的, 那么在此处调用用户设定的成功回调
userOptions.success && userOptions.success(resultData);
} else {
$dfd.resolve();
userOptions.success && userOptions.success();
}
},
/**
* 构建请求参数对象
* @param {string} serviceName 方法名
* @param {object} input 传入的参数对象
* @param {function} userSuccess 成功回调
* @param {function} userError 失败回调
* @param {object} ajaxParams 其他参数
* @returns object
*/
buildServiceRequest: function (serviceName, input, userSuccess, userError, ajaxParams) {
// 这里是根据后台要求构建的
var requestData = {
MethodAlias: serviceName, // 方法名
Parameter: input // 传递的参数
};
var request = $.extend({}, ajaxParams, {
// 这里要对传递的 JSON 字符串参数数据使用 escape 方法进行编码
// ‘data=’ 是根据项目约定增加的,与 contentType 相对应
data: ‘data=’ + escape(ajaxRequest.json.stringify(requestData)),
success: userSuccess,
error: function (jqXHR, textStatus, errorThrown) {
// 这里是在请求出错的时候做一个统一处理, 输出方法名和错误对象
console.log(serviceName, jqXHR);
if (userError && (typeof userError === ‘function’)) {
userError(jqXHR, textStatus, errorThrown);
}
}
});
return request;
},
// 构建参数对象, 生成唯一标识, 触发请求
triggerService: function (serviceName, input, success, error, ajaxParams) {
// 构建参数对象
var request = ajaxRequest.ajax.buildServiceRequest(serviceName, input, success, error, ajaxParams);
// 生成此次 ajax 请求唯一标识
var requestId = requestIdentifier[serviceName] = generateGUID();
request.url = URL + requestId;
// 触发请求
return ajaxRequest.ajax(request, serviceName, requestId);
}
});
})(jQuery);
module.exports = ajaxRequest;