Невозможная встроенная задержка/сон Javascript

Есть функция 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. Существует огромное количество других скриптов задержки:
Существует около 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>

пустой_фрейм.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;
}

медленная_страница.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 обращения к серверу)


person trex005    schedule 01.01.2011    source источник
comment
Разве вы не можете объединить new Function(){ return code; } с обратным вызовом setTimeout?   -  person meder omuraliev    schedule 02.01.2011
comment
Лично я думаю, что любой дизайн, который требует этого, очень ошибочен, но с академической точки зрения мне было бы интересно посмотреть, есть ли ответ.   -  person Brian Donovan    schedule 02.01.2011
comment
meder, setTimeout позволит продолжить выполнение Javascript, если у вас нет способа объединить те, о которых я не знаю. Я упомянул некоторые плагины jQuery, которые действительно делали очень умные вещи, но я проанализировал все, что смог найти, и ни один из них не работал для меня. Если вы сможете найти способ заставить его работать в приведенном выше примере, я буду в восторге! Брайан. Я согласен с вами на 99,999%, а несколько дней назад я бы сказал на 100% и поспорил бы с любым, кто сказал бы, что встроенная задержка необходима. К сожалению, у меня нет возможности исправить это здесь!   -  person trex005    schedule 02.01.2011
comment
Javascript по своей сути асинхронен. Это неправильная архитектура для использования с языком. Вы должны изменить свой API и предоставить функцию обратного вызова в качестве параметра, который будет вызываться после завершения вашей операции. Выполнение этого любым другим способом похоже на использование HTML div шириной 1 пиксель с фоновыми цветами для рисования изображения JPG. Конечно, вы можете это сделать, но это не то, что вы должны делать. Знайте свой язык и разрабатывайте свой код, думая о том, как этот язык предназначен для использования. Вы не пишете свой PHP, как вы пишете свой Javascript, так зачем же писать свой Javascript, как если бы он был C?   -  person dionyziz    schedule 02.01.2011
comment
Я бы очень хотел отдать 66% кредита lonesomeday и 34% кредита Зеки, потому что они объединили две идеи, которые они мне дали, и я заработал. к сожалению, я не знаю, как это сделать, поэтому мне придется отдать все это одинокому дню. Затем я опубликую комбинированный код, который отлично сработал.   -  person trex005    schedule 02.01.2011


Ответы (8)


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
}
person lonesomeday    schedule 02.01.2011
comment
Я мог бы добавить 100 URL-адресов на разные серверы, если бы мне было нужно (google, amazon, yahoo, whtismyip и т. д.) для вызовов ajax, через которые он мог бы пройти... Я бы все равно просто отбросил результаты... Я собираюсь чтобы немного поиграть с этой идеей, я предполагаю, что 1-2 звонка в любом случае будут большой задержкой. Я дам вам знать, как это происходит! - person trex005; 02.01.2011
comment
@ trex005 Учитывая политику одного и того же источника, вам потребуются URL-адреса в вашем собственном домене, иначе запросы будут мгновенно завершаться ошибкой. - person lonesomeday; 02.01.2011

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

person Zeki    schedule 02.01.2011
comment
неизменяемая функция вызывает единственную функцию, которую я контролирую, поэтому у меня не было бы возможности ввести ожидание перед этим. Однако я изучаю синхронный ajax. - person trex005; 02.01.2011
comment
Да, если вы это сделаете, вам не нужно переходить на страницу сотни раз, все, что вам нужно сделать, это заснуть в вашем php/jsp или чем-то еще, что вы используете. Добавьте 100 мс сна, и загрузка его 10 раз даст вам 1 секунду сна. - person Zeki; 02.01.2011
comment
Вы дали мне очень важный ключ к решению этой проблемы (скрипт usleep PHP), но я мог принять только один ответ. Хотел бы я принять оба. Благодарю вас! - person trex005; 02.01.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);

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

person James Long    schedule 02.01.2011
comment
Есть несколько проблем с этим, о которых я рассказал в своем вопросе. 1) Я не могу ничего изменить за пределами той функции, которую я указал. Причины этого сложны, поэтому я просто должен сказать, поверьте мне в этом. 2) Если вы посмотрите на мой код, вы на самом деле увидите, что я уже вызываю функцию, когда iFrame заканчивает загрузку. Это простая часть. Проблема в том, что мне нужно отложить остальную часть последующего выполнения кода до тех пор, пока это событие не будет запущено, и я не понимаю, как ваше решение вообще это делает. - person trex005; 02.01.2011
comment
Если у вас есть какой-либо способ заставить ваше решение работать с моим образцом, даже если вам придется изменить Blank_frame.html, я мог бы сделать это творчески. - person trex005; 02.01.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!

person Charles Bradshaw    schedule 27.09.2011

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

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   
}
person robert    schedule 01.07.2012
comment
Глядя на это, я не вижу, чем он отличается от '2. Цикл «Ничего не делать». Можете ли вы уточнить? - person trex005; 04.07.2012

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

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

person Dark Falcon    schedule 02.01.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.

person lonesomeday    schedule 02.01.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>

По сути, вы добавляете файл cookie iframe_loaded со значением yes. ИМО, лучше удалить куки, так как вам нужно сделать то же самое, если вы перезагрузите страницу. Вы также можете установить домен в вызове функции 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.

person Greg    schedule 02.01.2011