Невозможная встроенная задержка JavaScript / сон - PullRequest
8 голосов
/ 02 января 2011

Есть функция JavaScript, из-за которой у меня нулевой контроль над кодом, которая вызывает функцию, которую я написал. Моя функция использует DOM для генерации iFrame, определяет его src и затем добавляет его к другому элементу DOM. Однако, прежде чем моя функция вернется и, таким образом, позволит продолжить выполнение содержащей функции, обязательно, чтобы iFrame был полностью загружен.

Вот вещи, которые я пробовал и почему они не работают:

1. Опция SetTimeout:
99,999% случаев, это ответ. На самом деле, в последнее десятилетие, которым я занимался наставничеством в JavaScript, я всегда настаивал на том, чтобы код всегда мог быть подвергнут рефакторингу, чтобы использовать эту опцию, и никогда не думал, что существует сценарий, где это не так. Ну, я наконец-то нашла одну! Проблема заключается в том, что, поскольку моя функция вызывается как встроенная, если следующая строка выполняется до того, как мой iFrame завершит загрузку, она полностью нейтрализует мой сценарий, и с того момента, как мой сценарий завершается, внешний сценарий продолжается. Сортировка вызовов не будет работать

2. Цикл «Ничего не делать»:
Эта опция используется в то время как (// iFrame не загружен) {// ничего не делать}. Теоретически это не вернется, пока кадр не будет загружен. Проблема в том, что, так как это потребляет все ресурсы, iFrame никогда не загружается. Этот трюк, хотя и ужасно непрофессиональный, грязный и т. Д., Будет работать, когда вам просто нужна внутренняя задержка, но, поскольку мне требуется внешний поток для завершения, он не будет.
В FF через несколько секунд он останавливает выполнение сценария и появляется предупреждение о том, что существует сценарий, не отвечающий на запросы. Пока это оповещение активировано, iFrame может загрузить, а затем моя функция может вернуться, но браузер завис на 10 секунд, а затем требует, чтобы пользователь корректно отклонил ошибку, - нет дела.

3. Модель диалога:
Я был вдохновлен тем фактом, что всплывающее окно FF позволяло загружать iFrame, останавливая выполнение функции, и, подумав об этом, я понял, что это потому, что модальный диалог - это способ остановки выполнения, позволяющий другим потокам продолжать работу. ! Блестящий, поэтому я решил попробовать другие модальные варианты. Такие вещи, как alert () работают прекрасно! Когда он всплывает, хотя бы только на 1/10 секунды, iFrame может завершиться, и все прекрасно работает. И на случай, если 1/10 секунды не достаточно, я могу поместить диалог модели в цикл while из решения 2, и это обеспечит своевременную загрузку iFrame. Сладкий, верно? За исключением того факта, что теперь мне нужно вывести очень непрофессиональный диалог, чтобы пользователь мог его закрыть, чтобы запустить мой скрипт. Я боролся с собой за эту цену / выгоду от этого действия, но затем я столкнулся со сценарием, когда мой код вызывался 10 раз на одной странице! Необходимость отклонить 10 предупреждений перед доступом к странице ?! Это напоминает мне о страницах детского сценария конца 90-х, и это НЕ вариант.

4. Еще один сценарий задержки gazillion:
Существует около 10 функций jQuery delay или sleep, некоторые из них на самом деле довольно умно разработаны, но ни одна из них не сработала. Несколько вариантов прототипа, и опять же, ни один из найденных мной не смог бы это сделать! Около дюжины других библиотек и фреймворков утверждали, что у них есть то, что мне нужно, но, увы, все они сговорились, чтобы дать мне ложную надежду.

Я убежден, что, поскольку встроенный диалог моделей может остановить выполнение, в то же время позволяя другим потокам продолжаться, должен быть некоторый доступный код способ сделать то же самое без ввода данных пользователем.

Код буквально тысячи и тысячи строк и является частной собственностью, поэтому я написал этот небольшой пример проблемы, с которой вы можете работать. Важно отметить, что ТОЛЬКО код, который вы можете изменить, находится в функции 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>


ЗДЕСЬ ОТВЕТ, КОТОРЫЙ Я СДЕЛАЛ ИЗ КОМБИНИРОВАННЫХ ИДЕЙ ОТ РЕСПОНДЕРОВ!ВЫ, ребята, ROCK!
новый источник функции, которую мне было разрешено изменить:
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 была медленнее, он мог бы назвать ее 2ce (всего 3 обращения к серверу)

Ответы [ 8 ]

3 голосов
/ 02 января 2011

Да, тот факт, что javascript является однопоточным, действительно вас укусил.Вы можете использовать синхронный вызов ajax на целенаправленно медленной странице, чтобы эмулировать сон, но вы не получите желаемых результатов.Почему бы вам просто не убедиться, что ваш IFrame загружен до вызова неизменяемой функции?

2 голосов
/ 02 января 2011

NB Это очень глупо, и я бы не стал использовать его в реальной ситуации.Среди других потенциальных проблем, при наличии достаточного трафика, вы могли бы в конечном итоге DDOSing самостоятельно.

Вы могли бы создать функцию сна, делая неасинхронные (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
}
2 голосов
/ 02 января 2011

Что вам действительно нужно, так это событие, которое запускается при загрузке содержимого iFrame. Это на самом деле очень просто, потому что страница внутри iFrame имеет свои собственные события и может получать доступ к сценариям на родительской странице. Вы должны будете иметь возможность изменять содержимое iFrame.

В вашем iFrame вам понадобится этот кусок кода

// Use whichever DOMReady function you like, or window.onload would work
window.addEventListener('DOMContentLoaded', function() {
    if (parent.window.myFunction) {
        parent.window.myFunction();
    }
}, false);

Затем на вашей родительской странице создайте функцию myFunction и поместите в нее все сценарии, которые вам нужны для запуска. Это должно работать каждый раз.

Редактировать: Чтобы это заработало, вам действительно нужны две функции. Я предполагаю, что это на самом деле не вариант, поэтому мы взломаем одну функцию, которая будет содержать две функции, и вызовем нужную часть, когда нам это нужно.

function onlyThingYouCanChange(stringOrObject) {
    function createIFrame(objectToAppendIFrameTo) {
        // This comment represents all the code that appends your iFrame
    }
    function onIFrameReady() {
        // This comment represents all the stuff you want to happen when the iFrame is ready
    }

    // The bones of it
    if (stringOrObject === "iFrameLoaded") {
        onIFrameReady();
    } else {
        createIFrame(stringOrObject);
    }
}

Сценарий в iFrame теперь должен быть изменен на что-то вроде этого:

// Use whichever DOMReady function you like, or window.onload would work
window.addEventListener('DOMContentLoaded', function() {
    if (parent.window.onlyThingYouCanChange) {
        parent.window.onlyThingYouCanChange('iFrameLoaded');
    }
}, false);

Я не проверял это, но в теории это должно быть сделано

1 голос
/ 01 июля 2012

В этом случае может помочь рекурсивная функция.просто вызывайте функцию, пока глобальная переменная не покажет, что кадр загружен

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   
}
1 голос
/ 28 сентября 2011

Потрясающе простой; -} ответ, используя 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!

0 голосов
/ 02 января 2011

вы можете использовать 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 со значением yes,IMO лучше удалить куки, так как вам нужно сделать то же самое, если вы перезагрузите страницу.Вы также можете установить домен в вызове функции 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();
}

Так как в этом файле удаляется также файл, защищенный от сбоев.Надеюсь, это даст вам возможность поработать:)

Приветствия

G.

0 голосов
/ 02 января 2011

Другое решение, которое может быть неприменимо в зависимости от того, насколько вы упростили исходный код.Вы можете установить обработчик загрузки, затем выдать ошибку, а затем вызвать 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.Два разных пути отражают это.

Опять же, это хакерство и определенное злоупотребление функциональностью ошибок JS.

0 голосов
/ 02 января 2011

Почему нельзя изменить базовый код?Например, было бы довольно просто изменить основную функцию с

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 и используйте встроенную поддержку фьючерсов, чтобы справиться с этим.Обратите внимание, что Javascript 1.7 также поддерживает продолжения, но для их использования потребуется изменить код вручную.

...