在同步“Ajax”请求之前,强制Webkit(Safari和Chrome)重新渲染UI

7
在一个简单的基于Ajax的网站中,我们正在同步进行一些HttpRequest请求(我意识到“同步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);
    }

众所周知,同步请求会锁定用户界面。因此,不足为奇的是,在Safari和Chrome中白色遮罩从未出现过(有趣的是,在Firefox中出现了)。我尝试减缓响应速度并使用粉色遮罩,以使其非常明显,但它直到请求完成后才更新UI。如果省略'busyMask.className="Hidden"'部分,将显示遮罩-但只有在ajax请求完成后才会显示。
我看到很多强制UI重新绘制的技巧(例如为什么Google Chrome中的同步AJAX请求不起作用?http://ajaxian.com/archives/forcing-a-ui-redraw-from-javascript),但它们似乎都与试图显示实际的“永久”DOM或样式更新有关,而不是在进行同步请求时暂时显示样式更改。
那么有没有办法做到这一点,或者我正在进行一场失败的战斗?也许我们只需要针对最差表现的请求逐个切换为异步请求,这可能是解决学习曲线问题的不错方式...但我希望能在这里得到一个非传统的答案。
4个回答

6

为了回答这个问题,我将忽略您需要同步XHR请求的理由。我知道有时工作限制不允许使用最佳实践解决方案,因此我们会“凑合”以完成工作。所以我们现在聚焦于如何让您的同步ajax请求与可视更新一起正常工作!

考虑到您使用jQuery进行XHR请求,我假设可以使用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供您在几个浏览器中测试:jsfiddle example

"我将忽略正当理由" - 谢谢,我很感激。我会很快仔细阅读您的回复,看看能否将其应用于我们正在进行的工作中... - jwl
不幸的是,这种方法对我没有帮助,因为它仍然基本上是异步的——处理成功的代码与发出请求的代码不连贯。我在我的帖子中添加了一些代码块的澄清,以帮助澄清这一点。再次强调,这绝不是最佳实践……但是,在从发出请求的位置分离响应的处理可能对某些人来说过于繁琐。 - jwl
我还要指出,这并不是第一次使用同步作为简化开发模型的方法,例如可以看看Meteor:http://devopsangle.com/2012/04/25/pushing-data-not-pages-is-the-new-model-for-application-development/(尽管当然非常有争议)。 - jwl

0

你认为为什么:

doSomethingBeforeRequest();
response = synchronousAjax();
doSomethingToTheDataAfterRequest(response);

比那个“简单”多了:

doSomethingBeforeRequest();
properAjax(onSuccess(response){
   doSomethingToTheDataAfterRequest(response);
};

针对你的团队,我并不是在争论,但我真的很好奇这样做的理由是什么...

我能想到同步代码唯一的好处就是你可以省略几个花括号;但代价是冻结浏览器。

如果浏览器在请求之前没有完成重绘*,我能想到的唯一选择是使用延迟(正如BenSwayne所建议的);这将使代码像异步调用一样复杂,并且在请求期间仍会使浏览器无响应。

编辑(某种回答):

由于JavaScript缺乏线程;超时和ajax调用(允许浏览器在运行之前做其他事情;有点像线程语言中的sleep()),对于JavaScript编程来说是相当基础的。我知道一开始可能会有点难以理解(我也曾经困惑过),但实际上没有任何明智的方法可以避免学习它。

我知道人们可能会在需要按顺序向服务器发出多个请求时尝试进行同步调用;但你也可以通过嵌套多个调用来异步地完成这些操作,就像这样:

doSomethingBeforeRequest1();
ajax(onSuccess(response1){
   doSomethingToTheDataAfterRequest1(response1);
   ajax(onSuccess(response2){
       doSomethingToTheDataAfterRequest2(response2);
   };
};

但是,除非每个调用都相当缓慢并且您想在每个步骤指示进度之类的东西;否则,我建议您创建一个新服务来结合两个操作以进行一次调用。(如果您仍然需要在其他某些情况下单独使用它们,则此服务可以仅按顺序使用两个现有服务)。

(* 我更惊讶的是Firefox确实更新了dom...)


回答你的问题,主要是没有“doSomethingBeforeRequest”和“doSomethingToTheDataAfterRequest”这样的东西。相反,在一个函数中有一堆代码包括本地变量,然后我们需要获取一些数据,然后做更多的事情。要单独执行它,我们需要将那些变量放在我们以后可以获取的地方,这不是火箭科学,但是它是一种不同的编程模型。对于我来说,这并不是真正更困难,但我不是唯一的开发人员...我希望我们能够回到从一开始就做事情的方式,但当然这并不容易... - jwl
我猜你在这里是对信徒讲道,但主日学校需要一个更简单的版本。 - jwl

0

我做了一些测试并得出了一些结论: http://jsfiddle.net/xVHWs/1/

将您的代码更改为使用jQuery的hide()show()animate({ opacity: 'show' }, 'fast')animate({ opacity: 'hide' }, 'fast') 如果您在函数中不带时间参数或指定0毫秒时间,则Firefox会显示覆盖层并隐藏它,其他浏览器执行得太快,您看不到。在showhideanimate调用中放置100毫秒,您就会看到它。


不错的尝试,但UI更改直到请求完成后才会显示。 - jwl

-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});

你怎么能这么说呢?有什么参考资料吗?我猜它在所有浏览器上都可以工作。你认为哪一部分不会工作? - Zahid Riaz
从我的经验和研究来看,我可以确定我是正确的。浏览器中的JavaScript是每个Frame单线程的(除非你使用Workers)。你提供的代码与我编写的代码(在传递给$.ajax的选项中包含async)具有完全相同的效果,并且不会以完全相同的方式工作。你已经在所有浏览器中测试过它了吗? - jwl
我建议的代码与你的代码不同。将asyn false传递给ajax请求使得ajax异步,而不是执行语句异步。我的代码是使得每个语句都异步执行。如果你想像你自己的代码那样做,那么请在成功回调函数中放置busyMask.className="Hidden"; //sets display=none; - Zahid Riaz
抱歉,但是“$.ajaxSetup({async:false});”绝对不会使每个语句异步执行。JavaScript 中没有任何东西可以产生这样的效果。 - jwl
你是试过了还是只是根据你的假设在说?我知道这并不使每个语句同步。但确实会使ajax请求同步/阻塞。ajax请求后的语句直到获得ajax响应才会执行。但你编写的代码不同。它使ajax请求同步,但是ajax请求后的语句立即执行。在你的情况下,你必须在complete/success函数中编写代码。但对于我的情况,你不需要complete/success函数。这就是你的代码和我的代码之间的区别。如果你尝试一下就会知道。 - Zahid Riaz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接