在一个简单的基于Ajax的网站中,我们正在同步地发出一些HttpRequests请求(我意识到“同步Ajax”在某种程度上是矛盾的)。这是同步和异步完成的主要原因是为了简化一些参与者的编程模型(长篇故事)。
无论如何,我们希望能够在发出请求之前进行样式更改(特别是在Google搜索时使用半透明白色覆盖屏幕),然后在结果返回时将其删除。基本上这看起来像:
load:function(url) {
....
busyMask.className="Shown"; //display=block; absolute positioned full screen semi-transparent
var dta=$.ajax({type:"GET",dataType:"json",url:url,async: false}).responseText;
busyMask.className="Hidden"; //sets display=none;
...
return JSON.parse(dta);
}
众所周知,同步请求会锁定UI。所以毫不奇怪,白色叠加层从未出现在Safari和Chrome中(有趣的是在Firefox中)。我已经尝试减慢响应速度并使用粉红色叠加层,这样很明显,但它只是在请求完成之后才会更新UI。离开'busyMask.className =“Hidden”'部分将显示掩码 - 但仅在ajax请求完成后才会显示。
我已经看到许多强制UI重绘的技巧(例如Why HourGlass not working with synchronous AJAX request in Google Chrome?,http://ajaxian.com/archives/forcing-a-ui-redraw-from-javascript),但它们似乎都在试图显示实际的“永久”DOM或样式更新,而不是在发出同步请求时暂时显示样式更改。
那么有没有办法做到这一点,还是我正在打一场失败的战斗?可能我们只需要根据具体情况切换到异步请求,以处理性能最差的请求,这可能是解决学习曲线问题的一种不错的方法......但我希望有一个外部的方框在这里回答。
答案 0 :(得分:5)
对于这个问题的目的,我将忽略为什么需要同步XHR请求的理由。我知道有时工作限制不允许使用最佳实践解决方案,因此我们“做”以完成工作。因此,让我们专注于如何通过可视化更新为您提供同步ajax!
考虑到您正在为您的XHR请求使用jQuery,我将假设可以使用jQuery来显示/隐藏加载指示器并处理任何计时问题。
首先让我们在标记中设置一个加载指标:
<div id="loading" style="display:none;">Loading...</div>
现在让我们创建一些javascript:
// Show the loading indicator, then start a SYNCRONOUS ajax request
$('#loading').show().delay(100).queue(function() {
var jqxhr = $.ajax({type:"GET",dataType:"json",url:"www.yoururl.com/ajaxHandler",async: false}).done(function(){
//Do your success handling here
}).fail(function() {
//Do your error handling here
}).always(function() {
//This happens regardless of success/failure
$('#loading').hide();
});
$(this).dequeue();
});
首先,我们要显示加载指示符,然后在我们的同步XHR请求开始之前给浏览器稍微延迟重绘一下。通过使用jQuery的.queue()
方法,我们将.ajax()
调用放在默认的fx队列中,以便在.delay()
完成之后才会执行,这当然不会发生在.show()
完成。
jQuery .show()
方法将目标元素的CSS显示样式更改为阻止(或者在分配时恢复其初始值)。 CSS中的这种更改将导致浏览器尽快重排(也称为“重绘”)。延迟确保它能够在ajax调用之前重排。在所有浏览器中都不需要延迟,但不会超过您指定的毫秒数(通常,IE将成为限制因素,其他浏览器对1ms延迟感到高兴,IE需要一些东西重绘更重要。)
以下是您在几个浏览器中测试的问题:jsfiddle example
答案 1 :(得分:0)
你为什么这么想:
doSomethingBeforeRequest();
response = synchronousAjax();
doSomethingToTheDataAfterRequest(response);
比“更简单”:
doSomethingBeforeRequest();
properAjax(onSuccess(response){
doSomethingToTheDataAfterRequest(response);
};
为你的团队?我不是在争辩,但我对这种理由感到好奇......
我能想到的同步代码的唯一好处就是你节省了一些花括号;以冻结浏览器为代价。
如果浏览器在请求*之前没有完成重绘,我能想到的唯一选择就是使用延迟(正如BenSwayne建议的那样);这将使代码与异步调用一样复杂,并且仍然会在请求期间使浏览器无响应。
编辑(某种答案):
由于JavaScript缺少线程; timeouts和ajax调用(允许浏览器在运行之前执行其他操作;有点像使用线程语言的sleep()),对于编写JavaScript的方式来说是相当基础的。我知道它起初可能有点学习曲线(我知道我很困惑),但是没有任何明智的方法可以避免学习它。
我知道人们可能想要进行同步调用的一种情况是,必须按顺序向服务器发出多个请求;但你可以通过嵌套几个这样的调用来实现异步:
doSomethingBeforeRequest1();
ajax(onSuccess(response1){
doSomethingToTheDataAfterRequest1(response1);
ajax(onSuccess(response2){
doSomethingToTheDataAfterRequest2(response2);
};
};
但除非每次通话都很慢,你想在每一步或其他事情上表明进展;我宁愿建议您创建一个新服务,将两个操作与一个调用结合起来。 (如果在某些其他情况下仍需要它们,此服务可以按顺序使用两个现有服务。)
(*我对Firefox DOES更新dom感到惊讶......)
答案 2 :(得分:0)
我已经进行了一些测试,并提出了一些观点: http://jsfiddle.net/xVHWs/1/
更改您的代码以使用jQuery的hide()
,show()
或animate({ opacity: 'show' }, 'fast')
和animate({ opacity: 'hide' }, 'fast')
如果您在没有时间参数的情况下保留de函数或指定0 ms时间,Firefox将显示覆盖并隐藏它,其他浏览器会快速执行它以供您查看。在show
,hide
或animate
电话中放置100毫秒,您就会看到它。
答案 3 :(得分:-1)
$.ajaxSetup({async:false});
busyMask.className="Shown"; //display=block; absolute positioned full screen semi-transparent
var dta=$.ajax({type:"GET",dataType:"json",url:url}).responseText;
busyMask.className="Hidden"; //sets display=none;
$.ajaxSetup({async:true});