問題描述
在jquery或zepto下,循環調用同一個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);
}
});
}
結果

有些成功有些失敗了?這是為何?
問題解釋
觀察jsonp的源碼

/**
* jsonp請求
* @param options
* @param deferred
* @returns {*}
*/ $.ajaxJSONP = function(options, deferred){
//未設置type,就走 ajax 讓參數初始化.
//如直接調用ajaxJSONP,type未設置 if (!('type' in options)) return $.ajax(options)
var _callbackName = options.jsonpCallback, //回調函數名 callbackName = ($.isFunction(_callbackName) ? _callbackName() : _callbackName) || ('jsonp' + (++jsonpID)), //沒有回調,賦默認回調 script = document.createElement('script'),
originalCallback = window[callbackName], //回調函數 responseData,
//中斷請求,拋出error事件
//這里不一定能中斷script的加載,但在下面阻止回調函數的執行 abort = function(errorType) {
$(script).triggerHandler('error', errorType || 'abort')
},
xhr = { abort: abort }, abortTimeout
//xhr為只讀deferred if (deferred) deferred.promise(xhr)
//監聽加載完,加載出錯事件 $(script).on('load error', function(e, errorType){
//清除超時設置timeout clearTimeout(abortTimeout)
//刪除加載用的script。因為已加載完了 $(script).off().remove()
//錯誤調用error if (e.type == 'error' || !responseData) {
ajaxError(null, errorType || 'error', xhr, options, deferred)
} else {
//成功調用success ajaxSuccess(responseData[0], xhr, options, deferred)
}
//回調函數 window[callbackName] = originalCallback
if (responseData && $.isFunction(originalCallback))
originalCallback(responseData[0])
//清空閉包引用的變量值,不清空,需閉包釋放,父函數才能釋放。清空,父函數可以直接釋放 originalCallback = responseData = undefined
})
if (ajaxBeforeSend(xhr, options) === false) {
abort('abort')
return xhr
}
//回調函數設置,給后臺執行 window[callbackName] = function(){
/* console.info('callbackName arguments ');
console.info(arguments[0]);*/ responseData = arguments
/*console.info('responseData ');
console.info(responseData);*/ } //回調函數追加到請求地址 script.src = options.url.replace(/\?(.+)=\?/, '?$1=' + callbackName)
document.head.appendChild(script)
//超時處理,通過setTimeout延時處理 if (options.timeout > 0) abortTimeout = setTimeout(function(){
abort('timeout')
}, options.timeout)
return xhr
}

問題出在多線程處理。 當第一個jsonp剛執行完callback,賦了值時,此時,script的load事件還未觸發。第二個JSONP開始初始化。然后第一個script的load開始執行,但它的數據已被清掉了
第一個jsonp剛執行完callback,響應數據賦給了 responseData
//回調函數設置,給后臺執行 window[callbackName] = function(){
/* console.info('callbackName arguments ');
console.info(arguments[0]);*/ responseData = arguments
/*console.info('responseData ');
console.info(responseData);*/ }
第二個JSONP開始初始化。沒錯 responseData又被賦為undefine!!!

第一個script的load開始執行,responseData這時判斷絕對為undefined,為毛?因為這是閉包,引用最后一個responseData的值。只能進入error了。

問題修復
策略:
1, 修改jsonp源碼。在執行callback時,將responseData,傳入監聽函數。諸如function(data){ return function( ...onload... }(responseData);這個太麻煩,而且還得注意開源協議。
2,規避jsonp的響應。改成這樣一種寫法。原理是,只用jsonp發請求,然后后臺執行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會重寫window[checkLogin1].第二次請求將找不到。
//回調函數設置,給后臺執行 window[callbackName] = function(){
/* console.info('callbackName arguments ');
console.info(arguments[0]);*/ responseData = arguments
/*console.info('responseData ');
console.info(responseData);*/ }