Продолжайте загружать HTML, когда < script src = & # 39;... & # 39; загружается слишком долго?

Вопрос задан: 8 месяцев назад Последняя активность: 5 месяцев назад
up 3 down

Меня спросили на собеседовании:

если сценарий загружается более X секунд, весь   страница должна быть загружена (кроме этого синхронного скрипта). Обратите внимание, что мы   не должен изменять скрипт для запуска асинхронно (например, через   AppendChild). Нет сервера

Ну, у меня было несколько подходов:

-remove the dom
-window.abort
-mess up the document by document.write("'</s'+'cript>'")
-moving it to an iframe
-adding headers of CSP

Ничего не получалось.

Вот код (например) для удаления тега dom скрипта:

Обратите внимание, что после сценария есть текст. Так что ожидается увидеть текст через 1 сек.

<body>
  <script>
    setTimeout( function (){
 document.querySelector('#s').parentNode.removeChild(document.querySelector('#s'))

    },1000); //change here
  </script>

<script id ='s'  src="https://www.mocky.io/v2/5c3493592e00007200378f58?mocky-delay=40000ms" ></script>
    <span>!!TEXT!!</span>
</body>

Вопрос

Кажется, я не могу найти хитрость в том, как заставить страницу продолжить загрузку после определенного времени ожидания. Как я могу это сделать?

скрипка

Кстати, я видел интересные подходы вот

5 ответов

up 1 down

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

Учитывая их определения, задача невозможна с использованием того, что широко поддерживалось в 2017 году. Это возможно в 2019 году с использованием ServiceWorkers.

Как вы знаете, в браузере окно запускается в одном потоке, который запускает цикл обработки событий. Все, что является асинхронным, представляет собой некоторый объект отложенной задачи, который помещается в очередь для последующего выполнения.1. Связь между потоками окна и рабочими потоками асинхронна, обещания асинхронны, и обычно XHR выполняются асинхронно.

Если вы хотите вызвать синхронный скрипт, вам нужно сделать вызов, который блокирует цикл обработки событий. Однако в JavaScript нет прерываний или упреждающего планировщика, поэтому, пока цикл событий заблокирован, больше ничего не может быть выполнено, чтобы вызвать его прерывание. (То есть, даже если бы вы могли раскрутить рабочий поток, который ОС работает параллельно с основным потоком, рабочий поток ничего не может сделать, чтобы основной поток прервал чтение сценария.) Надеемся, что у вас может быть время ожидания при извлечении сценария, если есть способ установить время ожидания на уровне операционной системы для запроса TCP, а его нет. Единственный способ получить скрипт, кроме как через HTML script тег, который не может указать время ожидания, XHR (Короче для XMLHttpRequest) или же fetch где это поддерживается. Fetch имеет только асинхронный интерфейс, поэтому это не поможет. Пока можно сделать синхронный XHR запрос, согласно MDN (Сеть разработчиков Mozilla), многие браузеры имеют устарела синхронная поддержка XHR в основном потоке полностью. Еще хуже, XHR.timeout() "не должен использоваться для синхронных запросов XMLHttpRequests, используемых в среде документов, иначе бросить исключение InvalidAccessError."

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

Q.E.D

Частичное решение с работниками сервиса

@ Кайидо утверждает, что это может быть решено с помощью ServiceWorkers. Хотя я согласен с тем, что ServiceWorkers были разработаны для решения подобных проблем, я не согласен с тем, что они отвечают на этот вопрос по нескольким причинам. Прежде чем углубляться в них, позвольте мне сказать, что я думаю, что решение Kaiido отлично подходит для гораздо более общего случая, когда одностраничное приложение, полностью размещенное на HTTPS, реализует некоторый тайм-аут для ресурсов, чтобы предотвратить блокировку всего приложения, и моя критика этого решения скорее сводится к тому, что он является разумным ответом на вопрос интервью, чем к любой неудаче решения в целом.

  • По словам OP, вопрос возник «давным-давно», а поддержка сервисных работников в производственных выпусках Edge и Safari менее года. ServiceWorkers по-прежнему не считаются «стандартными» и AbortController до сих пор не полностью поддерживается.
  • Чтобы это работало, сервисные работники должны быть установлены и настроены с таймаутом до загрузки соответствующей страницы. Решение Kaiido загружает страницу для загрузки работников службы, а затем перенаправляет на страницу с медленным источником JavaScript. Хотя вы можете использовать clients.claim() чтобы запустить сервисных работников на странице, где они были загружены, они все равно не начнутся до тех пор, пока страница не будет загружена. (С другой стороны, они должны быть загружены только один раз, и они могут сохраняться при отключениях браузера, поэтому на практике вполне разумно предполагать, что работники сервиса были установлены.)
  • Реализация Kaiido налагает одинаковое время ожидания для всех выбранных ресурсов. Чтобы применить только к URL-адресу сценария, работнику службы необходимо предварительно загрузить URL-адрес сценария, прежде чем будет получена сама страница. Без какого-либо белого или черного списка URL-адресов тайм-аут будет применяться для загрузки самой целевой страницы, а также для загрузки источника сценария и всех других ресурсов на странице, синхронно или нет. Хотя, конечно, в этой среде Q & разумно представить пример, который не готов к работе, тот факт, что ограничение этого значения одним рассматриваемым URL означает, что URL необходимо отдельно жестко закодировать в работника службы, заставляет меня неудобно с этим как решение.
  • ServiceWorkers работают только с HTTPS-контентом. Я был неправ. Хотя сами ServiceWorkers должны загружаться через HTTPS, они не ограничиваются прокси-контентом HTTPS.

Тем не менее, я благодарю Kaiido за хороший пример того, что может сделать ServiceWorker.

1Есть отличная статья от Джейка Арчибальда, который объясняет, как асинхронные задачи ставятся в очередь и выполняются циклом событий окна.

up 1 down

Так как мое внимание было обращено на то, что вы сказали, что это интервью произошло «давно», то решение ниже, вероятно, не то, что они ожидали в то время.
Я признаю, что понятия не имею, чего они тогда ожидали, но с сегодняшними API это выполнимо:

Вы можете настроить ServiceWorker который будет обрабатывать все запросы вашей страницы, как прокси-сервер (но размещен в браузере), и который сможет прервать запрос, если он займет слишком много времени.
Но для этого нам нужно AbortController API, который до сих пор считается экспериментальной технологией, поэтому, опять же, это может быть не то, что они ожидали в качестве ответа во время этого интервью...

В любом случае, вот как может выглядеть наш ServiceWorker для выполнения запрошенной задачи:

self.addEventListener('fetch', async function(event) {
  const controller = new AbortController();
  const signal = controller.signal;

  const fetchPromise = fetch(event.request.url, {signal})
    .catch(err => new Response('console.log("timedout")')); // in case you want some default content

  // 5 seconds timeout:
  const timeoutId = setTimeout(() => controller.abort(), 5000);
  event.respondWith(fetchPromise);
});

И здесь это как plnkr. (Существует своего рода обман, потому что он использует две страницы, чтобы не ждать один раз полных 50, одну для регистрации ServiceWorker, а другую, где происходит медленная сеть. Но так как требования говорят, что медленная сеть происходит " В некоторых случаях ", я думаю, все еще допустимо предположить, что мы смогли зарегистрировать его хотя бы один раз.)
Но если это действительно то, что они хотели, то вам лучше было бы просто кешировать этот файл.

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

up 0 down

Если страница не достигает блока, который устанавливает window.isDone = true, страница будет перезагружена с помощью строки запроса, говорящей о том, что в первую очередь не следует добавлять медленный тег сценария на страницу.

я использую setTimeout(..., 0) чтобы записать тег сценария на страницу за пределами текущего блока сценария, это не делает загрузку асинхронной.

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

Это может не работать внутри jsBin, но если вы протестируете его локально на отдельной HTML-странице, это будет работать.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
  
  
</head>
<body>
  <script>
        
    setTimeout( function (){
       
     
      //    window.stop();
    
      window.location.href = window.location.href = "?prevent"
         
 
    }, 3000); //change here
  </script>
  
  <script>
    function getSearchParams(){
      return window.location.search.slice(1).split('&').reduce((obj, t) => {
        const pair = t.split('=')
        
        obj[pair[0]] = pair[1]
        
        return obj
      }, {})
    }

    console.log(getSearchParams())
    
    if(!getSearchParams().hasOwnProperty('prevent')) setTimeout(e => document.write('<script id ="s"  src="https://www.mocky.io/v2/5c3493592e00007200378f58?mocky-delay=4000ms"><\/script>'), 0)
  </script>
    <span>!!TEXT!!</span>
</body>
</html>

up -1 down

Вы можете использовать свойство async в теге script, он загрузит ваш файл js асинхронно. script src = "file.js" async>

up -1 down

Вот решение использует window.stop(), параметры запроса и php.

<html>
<body>
<?php if(!$_GET["loadType"] == "nomocky"):?>
<script>
var waitSeconds = 3; //the number of seconds you are willing to wait on the hanging script
var timerInSeconds = 0;
var timer = setInterval(()=>{
timerInSeconds++;
console.log(timerInSeconds);
  if(timerInSeconds >= waitSeconds){
    console.log("wait time exceeded. stop loading the script")
    window.stop();
    window.location.href = window.location.href + "?loadType=nomocky"
  }
},1000)
setTimeout(function(){
   document.getElementById("myscript").addEventListener("load", function(e){
     if(document.getElementById("myscript")){
        console.log("loaded after " + timerInSeconds + " seconds");
        clearInterval(timer);
     }
  })
},0)
</script>
<?php endif; ?>

<?php if(!$_GET["loadType"] == "nomocky"):?>
<script id='myscript' src="https://www.mocky.io/v2/5c3493592e00007200378f58?mocky-delay=20000ms"></script>
<?php endif; ?>

<span>!!TEXT!!</span>
</body>
</html>