問題描述
在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);*/ }