当<script src =“ ...” is =“” taking =“” too =“” long =“” to =“”加载时继续加载HTML?

时间:2019-01-12 10:22:53

标签: javascript html

= “”

在工作面试中有人问我:

  

如果脚本加载时间超过X秒,则整个   页面应加载(此同步脚本除外)。注意我们   不应将脚本更改为异步运行(例如,通过   appendChild)。没有服务器。

嗯,我有几种方法:

 -删除dom
-window.abort
-通过document.write(“''”)整理文档
-将其移动到iframe
-添加CSP标头
 

什么都没有。

这是例如删除脚本dom标签的代码:

请注意,脚本后有TEXT。因此,预计1秒钟后会看到文本。

  
  <脚本>
    setTimeout(function(){
 document.querySelector('#s')。parentNode.removeChild(document.querySelector('#s'))

    },1000); //在这里更改
  


     !! TEXT !! 

 

问题

我似乎找不到使页面超时后继续加载的窍门。我该怎么办?

提琴

顺便说一句,我已经看到了有趣的方法此处

5 个答案:

答案 0 :(得分:1)

由于已经引起我注意,您说这次采访发生在“ 很久以前”上,因此下面的解决方案可能不是他们当时所期望的。
我承认我不知道他们当时的期望,但是使用当今的API,这是可行的:

您可以设置ServiceWorker,它将像处理代理一样处理页面中的所有请求(但托管在浏览器中),并且如果处理时间太长,则可以中止该请求。< br /> 但是要做到这一点,我们需要AbortController API,该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);
});

And here it is as a plnkr. (有一种作弊方式,它使用两个页面来避免等待整个50秒钟,一个页面用于注册ServiceWorker,另一个页面则用于发生慢速网络。但是由于要求说慢速网络是“偶尔”发生的,所以我认为假设我们至少能够注册一次仍然是有效的。)
但是,如果这确实是他们想要的,那么您甚至最好只是缓存此文件。

正如评论中所述,如果您遇到了此问题IRL,则一定要设法解决根本原因,而不是这种黑客攻击。

答案 1 :(得分:1)

我的猜测是,这是他们试图解决但没有解决的问题,因此他们要求候选人提供解决方案,任何拥有该解决方案的人都会对他们印象深刻。我希望这是一个棘手的问题,他们知道这一点,并想看看您是否这样做。

给出他们的定义,使用2017年得到广泛支持的任务是不可能的。使用ServiceWorkers可以在2019年实现。

如您所知,在浏览器中,窗口在运行事件循环的单个线程中运行。异步的一切都是某种延迟的任务对象,该对象被放入队列中以供以后执行 1 。窗口的线程和工作线程之间的通信是异步的,Promises是异步的,并且通常XHR是异步完成的。

如果要调用同步脚本,则需要进行阻止事件循环的调用。但是,JavaScript没有中断或抢先式调度程序,因此,在事件循环被阻止时,没有其他任何事情可以运行来使其中止。 (也就是说,即使您可以启动OS与主线程并行运行的工作线程,工作线程也无法采取任何措施使主线程中止脚本的读取。)希望您可以在脚本的获取上拥有一个超时,如果有一种方法可以在TCP请求上设置操作系统级别的超时,而没有。除了无法通过HTML script标签获得脚本(无法指定超时)之外,获取脚本的唯一方法是XHRXMLHttpRequest的缩写)或fetch,其中它被支持。 Fetch仅具有异步接口,因此没有帮助。根据MDN(Mozilla开发人员网络)的说法,尽管可以发出同步XHR请求,但许多浏览器完全拥有deprecated synchronous XHR support on the main thread。更糟糕的是,XHR.timeout()“不应用于文档环境中使用的同步XMLHttpRequests请求,否则它将throw an InvalidAccessError exception。”

因此,如果阻塞主线程以等待JavaScript的同步加载,则在确定加载时间太长时,无法终止加载。如果不阻塞主线程,则脚本将直到页面加载后才执行。

Q.E.D

与服务人员的部分解决方案

@Kaiido认为这可能是solved with ServiceWorkers。尽管我同意ServiceWorkers旨在解决此类问题,但我不同意他们出于某些原因回答此问题。在深入探讨它们之前,我想说Kaiido的解决方案在更普遍的情况下是很好的,即完全由HTTPS托管的Single Page App实现某种类型的资源超时,以防止整个应用程序锁定,我对该解决方案的批评更多地是,它是对面试问题的一个合理答案,而不是整体上没有任何解决方案。

    OP表示,这个问题来自“很久以前”,Edge和Safari生产版本中的服务人员支持还不到一年。仍然不认为ServiceWorkers是“标准”的,并且今天AbortController仍未得到完全支持。
  • 为使此工作正常进行,必须在装入有问题的页面之前安装并配置超时的Service Worker。 Kaiido的解决方案加载页面以加载服务工作者,然后使用速度较慢的JavaScript源重定向到该页面。尽管您可以使用clients.claim()在加载它们的页面上启动服务工作者,但是直到页面加载之后,服务工作者才开始启动。 (另一方面,它们只需要加载一次,并且它们可以在浏览器关闭期间持续存在,因此在实践中,假设已经安装了Service Worker并不是完全的。)
  • Kaiido的实现对所有获取的资源施加相同的超时。为了仅应用到有问题的脚本URL,在提取页面本身之前,服务工作人员将需要预加载脚本URL。如果没有某种URL的白名单或黑名单,则超时将适用于加载目标页面本身以及加载脚本源和页面上的所有其他资产(无论是否同步)。虽然在此问答环境中提交一个尚不适合生产的示例当然是合理的,但是将其限制为一个有问题的URL的事实意味着该URL需要单独硬编码到服务工作者中,这使我感到不舒服这是解决方案。
  • ServiceWorkers仅处理HTTPS内容。我错了。尽管ServiceWorkers本身必须通过HTTPS进行加载,但它们不仅限于代理HTTPS内容。

也就是说,我感谢Kaiido提供了ServiceWorker可以做的很好的例子。

1 Jake Archibald有一个excellent article,解释了窗口的事件循环如何对异步任务进行排队和执行。

答案 2 :(得分:0)

如果页面没有到达设置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>

答案 3 :(得分:-1)

这是使用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>

答案 4 :(得分:-1)

您可以在脚本标签中使用async属性,它将异步加载您的js文件。 脚本src =“ file.js” async>