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