問題描述
在jquery或zepto下,循環(huán)調(diào)用同一個(gè)jsonp
for(var i = 0;i<5;i++){
$.ajax({
url:'https://m.suning.com/authStatus?callback=checkLogin1&_=1430100870770',
dataType:'jsonp',
jsonpCallback:'checkLogin1',
success:function(data){
console.info('success');
},
error:function(xhr,e){
console.error(e);
}
});
}
結(jié)果
有些成功有些失敗了?這是為何?
問題解釋
觀察jsonp的源碼
/**
* jsonp請(qǐng)求
* @param options
* @param deferred
* @returns {*}
*/ $.ajaxJSONP = function(options, deferred){
//未設(shè)置type,就走 ajax 讓參數(shù)初始化.
//如直接調(diào)用ajaxJSONP,type未設(shè)置 if (!('type' in options)) return $.ajax(options)
var _callbackName = options.jsonpCallback, //回調(diào)函數(shù)名 callbackName = ($.isFunction(_callbackName) ? _callbackName() : _callbackName) || ('jsonp' + (++jsonpID)), //沒有回調(diào),賦默認(rèn)回調(diào) script = document.createElement('script'),
originalCallback = window[callbackName], //回調(diào)函數(shù) responseData,
//中斷請(qǐng)求,拋出error事件
//這里不一定能中斷script的加載,但在下面阻止回調(diào)函數(shù)的執(zhí)行 abort = function(errorType) {
$(script).triggerHandler('error', errorType || 'abort')
},
xhr = { abort: abort }, abortTimeout
//xhr為只讀deferred if (deferred) deferred.promise(xhr)
//監(jiān)聽加載完,加載出錯(cuò)事件 $(script).on('load error', function(e, errorType){
//清除超時(shí)設(shè)置timeout clearTimeout(abortTimeout)
//刪除加載用的script。因?yàn)橐鸭虞d完了 $(script).off().remove()
//錯(cuò)誤調(diào)用error if (e.type == 'error' || !responseData) {
ajaxError(null, errorType || 'error', xhr, options, deferred)
} else {
//成功調(diào)用success ajaxSuccess(responseData[0], xhr, options, deferred)
}
//回調(diào)函數(shù) window[callbackName] = originalCallback
if (responseData && $.isFunction(originalCallback))
originalCallback(responseData[0])
//清空閉包引用的變量值,不清空,需閉包釋放,父函數(shù)才能釋放。清空,父函數(shù)可以直接釋放 originalCallback = responseData = undefined
})
if (ajaxBeforeSend(xhr, options) === false) {
abort('abort')
return xhr
}
//回調(diào)函數(shù)設(shè)置,給后臺(tái)執(zhí)行 window[callbackName] = function(){
/* console.info('callbackName arguments ');
console.info(arguments[0]);*/ responseData = arguments
/*console.info('responseData ');
console.info(responseData);*/ } //回調(diào)函數(shù)追加到請(qǐng)求地址 script.src = options.url.replace(/\?(.+)=\?/, '?$1=' + callbackName)
document.head.appendChild(script)
//超時(shí)處理,通過setTimeout延時(shí)處理 if (options.timeout > 0) abortTimeout = setTimeout(function(){
abort('timeout')
}, options.timeout)
return xhr
}
問題出在多線程處理。 當(dāng)?shù)谝粋€(gè)jsonp剛執(zhí)行完callback,賦了值時(shí),此時(shí),script的load事件還未觸發(fā)。第二個(gè)JSONP開始初始化。然后第一個(gè)script的load開始執(zhí)行,但它的數(shù)據(jù)已被清掉了
第一個(gè)jsonp剛執(zhí)行完callback,響應(yīng)數(shù)據(jù)賦給了 responseData
//回調(diào)函數(shù)設(shè)置,給后臺(tái)執(zhí)行 window[callbackName] = function(){
/* console.info('callbackName arguments ');
console.info(arguments[0]);*/ responseData = arguments
/*console.info('responseData ');
console.info(responseData);*/ }
第二個(gè)JSONP開始初始化。沒錯(cuò) responseData又被賦為undefine?。?!
第一個(gè)script的load開始執(zhí)行,responseData這時(shí)判斷絕對(duì)為undefined,為毛?因?yàn)檫@是閉包,引用最后一個(gè)responseData的值。只能進(jìn)入error了。
問題修復(fù)
策略:
1, 修改jsonp源碼。在執(zhí)行callback時(shí),將responseData,傳入監(jiān)聽函數(shù)。諸如function(data){ return function( ...onload... }(responseData);這個(gè)太麻煩,而且還得注意開源協(xié)議。
2,規(guī)避jsonp的響應(yīng)。改成這樣一種寫法。原理是,只用jsonp發(fā)請(qǐng)求,然后后臺(tái)執(zhí)行window.callback。
window.checkLogin1 = function(data){
console.info('checkLogin1 success');
console.info(data);
}
for(var i = 0;i<5;i++){
$.ajax({
url:'https://m.suning.com/authStatus?callback=checkLogin1&_=1430100870770',
dataType:'jsonp' }); }
切記不能加 jsonpCallback:‘checkLogin1’.原因是,jsonp會(huì)重寫window[checkLogin1].第二次請(qǐng)求將找不到。