在工作面试中有人问我:
如果脚本加载时间超过X秒,则整个 页面应加载(此同步脚本除外)。注意我们 不应将脚本更改为异步运行(例如,通过 appendChild)。没有服务器。
嗯,我有几种方法:
-删除dom
-window.abort
-通过document.write(“''”)整理文档
-将其移动到iframe
-添加CSP标头
什么都没有。
这是例如删除脚本dom标签的代码:
请注意,脚本后有TEXT。因此,预计1秒钟后会看到文本。
<脚本>
setTimeout(function(){
document.querySelector('#s')。parentNode.removeChild(document.querySelector('#s'))
},1000); //在这里更改
!! TEXT !!
问题
我似乎找不到使页面超时后继续加载的窍门。我该怎么办?
顺便说一句,我已经看到了有趣的方法此处
答案 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
标签获得脚本(无法指定超时)之外,获取脚本的唯一方法是XHR
(XMLHttpRequest
的缩写)或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实现某种类型的资源超时,以防止整个应用程序锁定,我对该解决方案的批评更多地是,它是对面试问题的一个合理答案,而不是整体上没有任何解决方案。
AbortController
仍未得到完全支持。
clients.claim()
在加载它们的页面上启动服务工作者,但是直到页面加载之后,服务工作者才开始启动。 (另一方面,它们只需要加载一次,并且它们可以在浏览器关闭期间持续存在,因此在实践中,假设已经安装了Service Worker并不是完全的。)也就是说,我感谢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>