首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 网站开发 > JavaScript >

应用jquery.form.js的ajaxsubmit方法提交数据的Bug

2012-11-23 
使用jquery.form.js的ajaxsubmit方法提交数据的Bug周五同事遇到一个很奇怪的问题,调到下班,虽然问题解决了

使用jquery.form.js的ajaxsubmit方法提交数据的Bug

周五同事遇到一个很奇怪的问题,调到下班,虽然问题解决了,但是不知道问题的具体原因,回来翻了翻代码,才发现症结所在,下面就分享出来,供遇到同样问题的同行们参考:

?

先把问题描述一下,做的功能是使用ajax向后台来提交数据,为了向用户进行很好的错误提示,后台中将出现错误时的错误原因返回给前端,前端使用jquery.form.js的ajaxsubmit来提交数据,并在success方法中提示“操作成功”,在error方法中提示错误原因。整个form提交的数据包括一些简单的input和一个文件的上传。下面是代码:

?

前端JSP代码:

< form id ="wfAuditForm" method ="post" enctype ="multipart/form-data">< input type ="file" name ="posterUrlUploadPath" id ="posterUrlUploadPath" class ="fileUpload" title ="上传图片" />

?

前端JS代码:

$("#wfAuditForm").ajaxSubmit({                    type: 'post',                    url: "data/resource/picture/save" ,                    success: function(data){                        alert( "success");                        $( "#wfAuditForm").resetForm();                    },                    error: function(XmlHttpRequest, textStatus, errorThrown){                        alert( "error");                    }                });
?

后台:

public void save(HttpServletResponse response, HttpServletRequest request, Integer hasUpload,PictureResource pic) {     response.setStatus(HttpServletResponse. SC_CONFLICT);}

?

问题是当提交的数据中file标签里面有值的话(有文件需要上传),即时后台返回的状态码不是200,也会触发js的success方法。

?

当然第一时间想到的是不是返回的状态码不是预期中的,于是使用了firebug对于通信进行了抓包,抓包后发现返回的的确是409(SC_CONFLICT),但是触发的还是success上面。后来意识到这种问题只有当有文件需要上传的时候才会发现,因此怀疑form提交的时候返回了两次response,一次是文件流从客户端到服务端的过程,一次是真正的数据提交的过程,因此使用了wireshark抓了几次包,抓出来的报文显示的确是只返回了一次response(当有文件上传的时候,会出现一个redirect的报文,这个在后面的博文中会有分析),这个说明跟http的网络通信及服务端处理没有关系。

?

问题到底出在什么地方呢?再次回过头来读jquery.form.js的代码,发现这段代码中有这么一段很可疑:

var found = false;    for ( var j=0; j < files.length; j++)        if (files[j])            found = true;    if (options.iframe || found) // options.iframe allows user to force iframe mode        fileUpload();    else        $.ajax(options);

这段代码的第一个for循环是遍历form中所有的file标签,一旦其中的一个file标签里面有值,就将found设置了true。后面的代码就是根据found来进行判断了,如果found为真(有需要上传的文件)将调用fileUpload方法,否则调用jquery的ajax方法。根据上面的现象描述,问题可能出现在fileUpload方法中。下面我们再看fileUpload方法:

// private function for handling file uploads (hat tip to YAHOO!)    function fileUpload() {        var form = $form[0];        var opts = $.extend({}, $.ajaxSettings, options);                var id = 'jqFormIO' + $.fn.ajaxSubmit.counter++;        var $io = $('<iframe id="' + id + '" name="' + id + '" />');        var io = $io[0];        var op8 = $.browser.opera && window.opera.version() < 9;        if ($.browser.msie || op8) io.src = 'javascript:false;document.write("");';        $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });        var xhr = { // mock object            responseText: null,            responseXML: null,            status: 0,            statusText: 'n/a',            getAllResponseHeaders: function() {},            getResponseHeader: function() {},            setRequestHeader: function() {}        };                var g = opts.global;        // trigger ajax global events so that activity/block indicators work like normal        if (g && ! $.active++) $.event.trigger("ajaxStart");        if (g) $.event.trigger("ajaxSend", [xhr, opts]);                var cbInvoked = 0;        var timedOut = 0;                // take a breath so that pending repaints get some cpu time before the upload starts        setTimeout(function() {            $io.appendTo('body');            // jQuery's event binding doesn't work for iframe events in IE            io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);                        // make sure form attrs are set            var encAttr = form.encoding ? 'encoding' : 'enctype';            var t = $form.attr('target');            $form.attr({                target:   id,                method:  'POST',                encAttr: 'multipart/form-data',                action:   opts.url            });            // support timout            if (opts.timeout)                setTimeout(function() { timedOut = true; cb(); }, opts.timeout);            form.submit();            $form.attr('target', t); // reset target        }, 10);                function cb() {            if (cbInvoked++) return;                        io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);            var ok = true;            try {                if (timedOut) throw 'timeout';                // extract the server response from the iframe                var data, doc;                doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;                xhr.responseText = doc.body ? doc.body.innerHTML : null;                xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;                                if (opts.dataType == 'json' || opts.dataType == 'script') {                    var ta = doc.getElementsByTagName('textarea')[0];                    data = ta ? ta.value : xhr.responseText;                    if (opts.dataType == 'json')                        eval("data = " + data);                    else                        $.globalEval(data);                }                else if (opts.dataType == 'xml') {                    data = xhr.responseXML;                    if (!data && xhr.responseText != null)                        data = toXml(xhr.responseText);                }                else {                    data = xhr.responseText;                }            }            catch(e){                ok = false;                $.handleError(opts, xhr, 'error', e);            }            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it            if (ok) {                opts.success(data, 'success');                if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);            }            if (g) $.event.trigger("ajaxComplete", [xhr, opts]);            if (g && ! --$.active) $.event.trigger("ajaxStop");            if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');            // clean up            setTimeout(function() {                 $io.remove();                 xhr.responseXML = null;            }, 100);        };
?

很明显,这是通过使用隐藏iframe来模拟ajax实现的文件上传(参见该方法的介绍博文《谈谈使用iFrame模拟Ajax的问题》)。注意在方法cb中有这么一段代码:

catch(e){                ok = false;                $.handleError(opts, xhr, 'error', e);            }            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it            if (ok) {                opts.success(data, 'success');                if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);            }

?

从这个代码中可以看出,仅仅是当出现异常的时候(关于js异常的情况,请参见介绍博文《Javascript的异常处理介绍》),才会触发我们设定的error方法,其余情况都会触发success,也就是说即时http返回的不是200,而是其他的错误码,只要不出现异常就不会触发error方法!

?

找到问题原因了,我们怎么来实现根据http返回的状态码来进行相应的处理呢?一种策略是将状态码写到返回的是text的文本中,然后在客户端根据文本进行判断。或许另外一种方法是重写这个cb方法,在其中根据http的状态码来进行不同的处理,不过我还没有找到获取返回的状态码的方法。

?

互联网码农一枚,欢迎微博互粉,进行交流:http://weibo.com/icemanhit

1 楼 vvvpig 2012-09-02   捕获complete事件不可以吗,应该都是标准的ajax事件
http://api.jquery.com/jQuery.ajax/
complete(jqXHR, textStatus)Function, Array 2 楼 jianfeihit 2012-09-02   vvvpig 写道捕获complete事件不可以吗,应该都是标准的ajax事件
http://api.jquery.com/jQuery.ajax/
complete(jqXHR, textStatus)Function, Array
也可以,使用complete方法也是需要进行判断,因为只要程序不抛出异常,textStatus都会返回success。在这种情况下,我觉得使用complete方法与使用success方法差不多,除非程序在运行期抛出异常了

热点排行