簡要說明
前面我寫了一篇《封裝 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;