不可能的内联Javascript延迟/休眠

8
有一个JavaScript函数,我无法控制该代码,它调用了我编写的一个函数。我的函数使用DOM生成一个iFrame,定义其src,然后将其附加到另一个DOM元素上。然而,在我的函数返回之前,并允许包含函数的持续执行之前,完全加载iFrame是至关重要的。
这里是我尝试过的事情以及它们不起作用的原因:
1. SetTimeout选项:99.999%的时间,这是正确答案。事实上,在过去的十年中,我一直在指导JavaScript,我始终坚信可以重构代码以使用此选项,并且从未相信存在无法使用此选项的情况。好吧,我终于找到了一个!问题在于,因为我的函数是内联调用的,如果下一行在我的iFrame完成加载之前执行,它会完全禁用我的脚本,而自从我的脚本完成后,外部脚本就会继续运行。某种回调将无法工作。
2. “什么都不做”的循环:您使用while(// iFrame未加载){//什么也不做}这个选项。理论上,这不会返回,直到框架加载。问题在于,由于这占用了所有资源,因此iFrame永远不会加载。尽管这个技巧非常不专业,肮脏等等,但当您只需要内联延迟时,它会起作用,但由于我需要完成外部线程,因此不起作用。在FF中,几秒钟后,它会暂停脚本,并弹出一个警报,指示有一个无响应的脚本。当该警报出现时,iFrame能够加载,然后我的函数能够返回,但是让浏览器冻结10秒钟,然后要求用户正确解除错误是行不通的。
3.模型对话框:我受到了FF弹出窗口允许iFrame在停止执行函数的同时加载的启发,并且考虑到这一点,我意识到这是因为模态对话框是一种停止执行但允许其他线程继续的方式!太棒了,所以我决定尝试其他模态选项。像alert()之类的东西非常好用!当它弹出时,即使只有1/10秒,iFrame也能够完成,所有工作都很顺利。如果1/10秒不足够,我可以将模型对话框放入解决方案2的while循环中,这将确保iFrame及时加载。很甜美,对吧?除了我现在必须弹出一个非常不专业的对话框,让用户解除才能运行我的脚本。我与自己争论这个行动的成本/效益,但是然后我遇到了一个场景,在该场景中我的代码在单个页面上被调用了10次!在访问页面之前必须解除10个警报吗?那让我想起了90年代末的脚本骗子页面,这绝不是一个选项。

4. 有无数的延时脚本:
大约有10个jQuery延迟或睡眠函数,其中一些实际上开发得相当巧妙,但都不起作用。有几个原型选项,但同样没有找到能够解决问题的!还有十几个库和框架声称他们有我需要的东西,但遗憾的是他们都让我失望了。

我确信,由于内置的模态对话框可以停止执行,同时允许其他线程继续执行,必须有某种可访问代码的方式来完成同样的事情而不需要用户输入。

代码实际上有成千上万行,是专有的,因此我为您编写了这个问题的小例子供您参考。重要的是要注意,您唯一能够更改的代码位于onlyThingYouCanChange函数中

测试文件:

<html>
<head>
</head>
</html>
<body>
<div id='iFrameHolder'></div>
<script type='text/javascript'>
function unChangeableFunction()
{
    new_iFrame = onlyThingYouCanChange(document.getElementById('iFrameHolder'));
    new_iFrame_doc = (new_iFrame.contentWindow || new_iFrame.contentDocument);
    if(new_iFrame_doc.document)new_iFrame_doc=new_iFrame_doc.document;
    new_iFrame_body = new_iFrame_doc.body;
    if(new_iFrame_body.innerHTML != 'Loaded?')
    {
        //The world explodes!!!
        alert('you just blew up the world!  Way to go!');
    }
    else
    {
        alert('wow, you did it!  Way to go!');
    }
}
var iFrameLoaded = false;
function onlyThingYouCanChange(objectToAppendIFrameTo)
{
    iFrameLoaded = false;
    iframe=document.createElement('iframe');
    iframe.onload = new Function('iFrameLoaded = true');
    iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure that must be maintained.
    objectToAppendIFrameTo.appendChild(iframe);
    var it = 0;
    while(!iFrameLoaded) //I put the limit on here so you don't 
    {
        //If I was able to put some sort of delay here that paused the exicution of the script, but did not halt all other browser threads, and did not require user interaction we'd be golden!
        //alert('test'); //This would work if it did not require user interaction!
    }
    return iframe;
}
unChangeableFunction();
</script>
</body>

blank_frame.html:

<html>
<head>
</head>
<body style='margin:0px'>Loaded?</body>
</html>


这是我从回答者的想法中结合而成的答案!你们太棒了!
我被允许更改的函数的新源代码:

function onlyThingYouCanChange(objectToAppendIFrameTo)
{
    iFrameLoaded = false;
    iframe=document.createElement('iframe');
    iframe.onload = new Function('iFrameLoaded = true');
    iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure that must be maintained.
    objectToAppendIFrameTo.appendChild(iframe);
    var it = 0;
    while(!iFrameLoaded) //I put the limit on here so you don't
    {
        if (window.XMLHttpRequest)
        {
            AJAX=new XMLHttpRequest();
        }
        else
        {
            AJAX=new ActiveXObject("Microsoft.XMLHTTP");
        }
        if (AJAX)
        {
            AJAX.open("GET", 'slow_page.php', false);
            AJAX.send(null);
        }
        else
        {
            alert('something is wrong with AJAX!');
        }

        //If I was able to put some sort of delay here that paused the exicution of the script, but did not halt all other browser threads, and did not require user interaction we'd be golden!
        //alert('test'); //This would work if it did not require user interaction!
    }
    return iframe;
}

slow_page.php :

<?
usleep(100000);//sleep for 1/10th of a second, to allow iFrame time to load without DOSing our own server!
?>

我需要指出的是,我已经声明在那个函数之外没有其他东西可以更改,而添加php页面确实违反了这个“规则”,但在我的情况下,我能够做到这一点。如果我不能这样做,我可以调用blank_frame.html而不是slow_page.php,并且它只需要调用一次(每次框架加载2次),假设它响应时间与iFrame加载相同。如果由于某种原因iFrame加载速度较慢,则可能会调用2次(总共3次调用服务器)。


1
你不能将 new Function(){ return code; }setTimeout 的回调函数结合起来吗? - meder omuraliev
2
个人认为,任何需要这种情况的设计都存在严重缺陷,但从学术角度来看,我很想知道是否有答案。 - Brian Donovan
meder,setTimeout会允许JavaScript继续执行,除非您有一种我不知道的组合方式。我提到了一些做了一些非常聪明的事情的jQuery插件,但是我分析了所有我能找到的内容,没有一个适用于我。如果您能找到一种在上面的示例中使其工作的方法,我将感到高兴!Brian。我99.999%同意您的观点,几天前我会说100%,并会与任何认为行内延迟是必要的人争论。不幸的是,在这里我无法控制修复它! - trex005
JavaScript 本质上是异步的。这是与该语言一起使用的错误架构。您应该更改 API 并提供回调函数作为参数,在完成操作时调用该函数。以任何其他方式执行操作就像使用背景颜色为 JPG 图像绘制 1 像素宽的 HTML divs 一样。当然,您可以这样做,但这不是您应该做的事情。了解您的语言,并设计您的代码,考虑使用该语言的方式。您不会像编写 PHP 一样编写 JavaScript,那么为什么要将 JavaScript 写成 C 的样子呢? - dionyziz
我真的很想把66%的功劳归给lonesomeday,34%的功劳归给Zeki,因为是他们给了我两个结合起来才让我成功的想法。不幸的是,我不知道如何做到这一点,所以我只能全部归功于lonesomeday。然后,我将发布完美运行的组合代码。 - trex005
8个回答

3

是的,JavaScript 是单线程的,这在这里确实让人很困扰。你可以使用同步的 AJAX 调用到一个特意慢的页面来模拟休眠,但你得不到你想要的结果。为什么不在调用不可更改的函数之前确保你的 IFrame 已经加载完毕呢?


不可变函数调用我能控制的唯一函数,所以我没有任何办法在那之前引入等待。但我正在尝试同步的ajax。 - trex005
是的,如果你这样做,你就不需要一遍又一遍地刷新页面,你只需要在你的php/jsp或者其他任何你使用的语言中添加一个sleep。加入100毫秒的sleep,刷新10次将使您有1秒的间隔。 - Zeki
你在解决这个问题(usleep PHP脚本)时给了我一个非常重要的关键,但我只能接受一个答案。我希望我能够接受两个答案。谢谢! - trex005

2

NB 这种方法非常粗糙,不建议在任何真实场景中使用。除了其他可能的问题之外,如果流量足够大,您可能会自己遭受DDOS攻击。

您可以通过创建非异步(A)JAX调用来创建睡眠功能。在一些旧版浏览器中,这可能会冻结所有内容,但至少不需要任何用户响应。

while (!iFrameLoaded)
{
    if (XMLHTTPRequest) {
        var request = new XMLHttpRequest();
    } else {
        var request = new ActiveXObject("Microsoft.XMLHTTP");
    }

    request.open('GET', 'anyoldfile.htm', false);
    request.send();

    // check if the iframe is loaded and set iFrameLoaded
}

如果需要的话,我可以在不同的服务器上放置100个URL(如Google、Amazon、Yahoo、whtismyip等),供ajax调用循环使用...反正我只会丢弃结果...我会试着玩一下这个想法,我猜1-2个调用已经足够延迟了。我会告诉你进展如何的! - trex005
3
由于同源策略的限制,你需要使用属于自己域名下的 URL,否则请求将会立即失败。 - lonesomeday

2
你需要的是在iFrame内容加载完成时触发一个事件。这实际上非常容易,因为iFrame内部的页面有自己的事件,并且可以访问父页面上的脚本。但你需要能够更改iFrame的内容。
在你的iFrame中,你将需要这段代码:
// 使用任何你喜欢的DOMReady函数,或者window.onload也可以 window.addEventListener('DOMContentLoaded', function() { if (parent.window.myFunction) { parent.window.myFunction(); } }, false);
然后在你的父页面中,创建一个名为“myFunction”的函数,并将需要触发的所有脚本放在里面。这样每次都应该可以正常工作。
编辑:为了使其正常工作,你实际上需要两个函数。我假设这真的不是一个选项,所以我们将在一个函数中包含两个函数,并在需要时调用正确的部分。
function onlyThingYouCanChange(stringOrObject) { function createIFrame(objectToAppendIFrameTo) { // 这个注释代表了添加iFrame的所有代码 } function onIFrameReady() { // 这个注释代表了当iFrame准备好时要发生的所有事情 }
// 基本操作 if (stringOrObject === "iFrameLoaded") { onIFrameReady(); } else { createIFrame(stringOrObject); } }
现在,iFrame中的脚本应该更改为以下内容:
// 使用任何你喜欢的DOMReady函数,或者window.onload也可以 window.addEventListener('DOMContentLoaded', function() { if (parent.window.onlyThingYouCanChange) { parent.window.onlyThingYouCanChange('iFrameLoaded'); } }, false);
理论上来说,这应该可以正常工作。

这里有几个问题,我在我的问题中已经涵盖了。1)我无法更改指定函数之外的任何内容。原因很复杂,所以我只能说请相信我。2)如果您查看我的代码,实际上会发现当iFrame完成加载时,我已经调用了一个函数。那是简单的部分。问题是,我需要延迟其余后续代码的执行,直到该事件被触发,而我不知道您的解决方案如何做到这一点。 - trex005
如果您能够以任何方式让您的解决方案与我的示例配合工作,即使您必须更改blank_frame.html,我也可能以创造性的方式实现。 - trex005

1
一个使用XPCOM的惊人简单的 ;-} 答案:
// Get instance of the XPCOM thread manager.
var threadManager=Components.classes['@mozilla.org/thread-manager;1'].getService(
                      Components.interfaces.nsIThreadManager);
// Release current thread.
function doThread() {threadManager.currentThread.processNextEvent(false);};

// Event enabled delay, time in ms.
function delay(time) {
  var end;
  var start=Date.now();
  do {
    end=Date.now();
    doThread();
  } while ((end-start) <= time);
}

适用于最新版本的Firefox。抱歉,对于Explorer没有希望!


1
一个递归函数可能会在这种情况下有所帮助。只需调用该函数,直到全局变量指示该框架已加载。
var iFrameStarted = false; //you need two global vars
var iFrameLoaded  = false;


function onlyThingYouCanChange(objectToAppendIFrameTo)
{
  if (iFrameLoaded=false)  // if the frame has loaded then you are done. skip everything and return iframe
  { if (iFrameStarted = false) //otherwise start the frame if it has not been
  {
   iFrameStarted = true;
   iframe=document.createElement('iframe');
   iframe.onload = new Function('iFrameLoaded = true');
   iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure
   objectToAppendIFrameTo.appendChild(iframe);
   var it = 0;
   for (i=0;i<10000;i++) {}  //slow down execution so you are not recursing yourself to death
   onlyThingYouCanChange(objectToAppendIFrameTo);   //start the recursion process

  }
  else  //the frame has been started so continue recursion until the frame loaded
  {
   for (i=0;i<10000;i++) {}  //slow down execution so you are not recursing yourself to death
   onlyThingYouCanChange(objectToAppendIFrameTo);   recursively call your function until the frame is loaded

  }

}

return iframe;  //you only get here when all the recursions are finished   
}

浏览一下,我没有看出它与“2. 什么也不做的循环”有什么不同。你能详细说明一下吗? - trex005

0
为什么不能修改基础代码?例如,更改核心功能可能相当简单,比如:
function unChangeableFunction()
{
    new_iFrame = onlyThingYouCanChange(document.getElementById('iFrameHolder'));
    new_iFrame_doc = (new_iFrame.contentWindow || new_iFrame.contentDocument);
    if(new_iFrame_doc.document)new_iFrame_doc=new_iFrame_doc.document;
    new_iFrame_body = new_iFrame_doc.body;
    if(new_iFrame_body.innerHTML != 'Loaded?')
    {
        //The world explodes!!!
        alert('you just blew up the world!  Way to go!');
    }
    else
    {
        alert('wow, you did it!  Way to go!');
    }
}

转换成类似这样的内容:
function unChangeableFunction()
{
    var new_iFrame = onlyThingYouCanChange(document.getElementById('iFrameHolder'));
    new_iFrame.onload = function()
    {
        new_iFrame_doc = (new_iFrame.contentWindow || new_iFrame.contentDocument);
        if(new_iFrame_doc.document)new_iFrame_doc=new_iFrame_doc.document;
        new_iFrame_body = new_iFrame_doc.body;
        if(new_iFrame_body.innerHTML != 'Loaded?')
        {
            //The world explodes!!!
            alert('you just blew up the world!  Way to go!');
        }
        else
        {
            alert('wow, you did it!  Way to go!');
        }
    };
}

如果这对您不起作用,那么如何透明地修改原始代码呢?使用Javascript Strands编译它,并使用内置的futures支持来处理它。请注意,Javascript 1.7也支持continuations,但需要手动更改代码才能使用它们。


0

另一种解决方案可能不适用,这取决于您简化了原始代码的程度。您可以设置一个onload处理程序,然后抛出一个错误,在您的onload处理程序中调用unChangeableFunction

function onlyThingYouCanChange(objectToAppendIFrameTo)
{
    // using global variable func_called
    if (!func_called) {
        func_called = true;
        var iframe=document.createElement('iframe');
        iframe.src = 'blank_frame.html';
        iframe.id = 'myIframe';
        iframe.onload = function() {
            unChangeableFunction();
        };
        objectToAppendIFrameTo.appendChild(iframe);

        throw new Error('not an error');
    } else {
        return document.getElementById('myIframe');
    }
}

此函数(例如unChangeableFunction)将被调用两次:一次在第一个实例中,然后在触发onload处理程序时再次调用。 两个不同的路径反映了这一点。

再次强调,这是hacky的,而且绝对滥用了JS的错误功能。


0

你可以像这样使用cookie和setTimeout:

在blank_frame.html中添加一个脚本:

<script type="text/javascript">
function deleteCookie(cookie_name)
{
  var cookie_date=new Date();
  cookie_date.setTime(cookie_date.getTime()-1);
  document.cookie=cookie_name+="=;expires="+cookie_date.toGMTString();
}
function setCookie(name,value,expires,path,domain,secure){
    document.cookie=name+"="+escape(value)+((expires)?"; expires="+expires.toGMTString():"")+((path)?"; path="+path:"")+((domain)?"; domain="+domain:"")+((secure)?"; secure":"");
}

window.onload=function(){
    setCookie('iframe_loaded','yes',false,'/',false,false);
}
</script>

基本上,您正在添加一个名为iframe_loaded的cookie,并将其值设置为yes。 我认为最好删除cookie,因为如果重新加载页面,则需要执行相同的操作。 您还可以在setCookie函数调用中设置域。

现在,在主文件中,我们将使用setTimeout和一个函数来检查cookie是否存在,如果存在,则该函数将像您的代码一样返回iframe:

function onlyThingYouCanChange(objectToAppendIFrameTo)
{
    function get_cookie(cookie_name){
        var results = document.cookie.match('(^|;) ?'+cookie_name+'=([^;]*)(;|$)');
        return results?unescape(results[2]):null;
    }
    function deleteCookie(cookie_name){
        var cookie_date=new Date();
        cookie_date.setTime(cookie_date.getTime()-1);
        document.cookie=cookie_name+="=;expires="+cookie_date.toGMTString();
    }

    iFrameLoaded = false;
    iframe=document.createElement('iframe');
    iframe.onload = new Function('iFrameLoaded = true');
    iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure that must be maintained.
    objectToAppendIFrameTo.appendChild(iframe);
    var it = 0;

    function checkiframe(){
        if(get_cookie('iframe_loaded')=="yes"){
            alert('iframe loaded');
            deleteCookie('iframe_loaded');
            return iframe;
        }else{
            setTimeout(checkiframe,1000);
        }
    }

    checkiframe();
}

作为故障保护措施,此文件中也删除了一个cookie。 希望这能给你提供一些帮助 :) 干杯 G.

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