Application.js script blocked in "queuing" state until images have loaded - javascript

Our site has an asynchronously loaded application.js:
<script async="async" src="//...application-123456.js"></script>
Additionally, we have a lot of third party scripts that (1) are asynchronously loaded, and (2) create in turn an async <script> tag where a bigger script is called.
Just to give an example, one of these third party scripts is Google's gpt.js (you can have a quick look to understand how it works).
Our problem is that, while all the third party scripts load asynchronously as expected, the application.js one gets stack in "queuing" status for more than 4 seconds.
I tried to change the script and make it load like the third party ones: create a <script> element, set the "src" attribute and load it:
<script async>
(function() {
var elem = document.createElement('script');
elem.src = 'http://...application-123456.js';
elem.async = true;
elem.type = 'text/javascript';
var scpt = document.getElementsByTagName('script')[0];
scpt.parentNode.insertBefore(elem, scpt);
})();
</script>
but nothing changed.
Then I studied the network cascade in a page of our site that almost doesn't contain images, and I saw that the queuing time was almost zero. I tried the same experiment in pages with different amounts of images, and saw that the queuing time proportionally increases in pages with more images.
I read this in Chrome's network cascade documentation:
QUEUING TIME: The request was postponed by the rendering engine because it's considered lower priority than critical resources (such as scripts/styles). This often happens with images.
Is it possible that for some reason the browser is marking our application.js as "lower priority"? I looked on the web and it seems that nobody has experienced problems with the queuing time. Anybody has an idea?
Thank you very much.

Browsers use a pre-loader to improve network utilisation. This article explains the concept.
In the Chrome Documentation you linked to above, it says the following about queuing:
If a request is queued it indicated that:
The request was postponed by: the rendering engine because it's considered lower priority than critical resources (such as scripts/styles). This often happens with images.
The request was put on hold to wait for an unavailable TCP
socket that's about to free up. The request was put on hold because the browser only allows six TCP connections per origin on HTTP 1.
Time spent making disk cache entries (typically very quick.)
The pre-loader would have retrieved the lightweight resources quickly, such as the styles and scripts, and then queued up the images because, as the criteria above suggests, only 6 TCP connections are permitted per origin. Therefore, this would explain the delay in the total response time.

Related

Continue to load HTML when <script src='...' is taking too long to load?

I was asked in a Job interview:
if a script takes more than X seconds to load , the whole
page should be loaded (except this synchronous script). Note that we
should not change the script to run asynchronously (for example, via
appendChild). No server.
Well, I had a couple of approaches :
-remove the dom
-window.abort
-mess up the document by document.write("'</s'+'cript>'")
-moving it to an iframe
-adding headers of CSP
Nothing worked.
Here is the code (for example) to remove the script dom tag :
Notice that there is TEXT after the script. So it is expected to see the text after 1 sec.
<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>
Question
I can't seem to find the trick for how can I make the page continue loading after a certain timeout. How can I do that?
Fiddle
BTW I've seen interesting approaches here
Since it has been pointed to my attention that you said this interview happened a "long time ago", below solution is probably not what they expected at that time.
I will admit I have no idea what they were expecting then, but with today's APIs, it is doable:
You can setup a ServiceWorker which will handle all the requests of your page as would do a proxy (but hosted in the browser), and which would be able to abort the request if it takes too long.
But to do so, we need the AbortController API which is still considered an experimental technology, so once again, that may not be what they expected as an answer during this interview...
Anyway, here is what our ServiceWorker could look like to accomplish the requested task:
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. (There is a kind of a cheat in that it uses two pages to avoid waiting once the full 50s, one for registering the ServiceWorker, and the other one where the slow-network occurs. But since the requirements say that the slow network happens "On occasions", I think it's still valid to assume we were able to register it at least once.)
But if that's really what they wanted, then you'd even be better just to cache this file.
And as said in comments, if you ever face this issue IRL, then by all means try to fix the root cause instead of this hack.
My guess is that this was a problem they were trying to solve but had not, so they were asking candidates for a solution, and they would be impressed with anyone who had one. My hope would be that it was a trick question and they knew it and wanted to see if you did.
Given their definitions, the task is impossible using what was widely supported in 2017. It is sort of possible in 2019 using ServiceWorkers.
As you know, in the browser, the window runs in a single thread that runs an event loop. Everything that is asynchronous is some kind of deferred task object that is put on a queue for later execution1. Communications between the window's threads and workers' threads are asynchronous, Promises are asynchronous, and generally XHR's are done asynchronously.
If you want to call a synchronous script, you need to make a call that blocks the event loop. However, JavaScript does not have interrupts or a pre-emptive scheduler, so while the event loop is blocked, there is nothing else that can run to cause it to abort. (That is to say, even if you could spin up a worker thread that the OS runs in parallel with the main thread, there is nothing the worker thread can do to cause the main thread to abort the read of the script.) The only hope you could have of having a timeout on the fetch of the script is if there were a way to set an operating-system-level timeout on the TCP request, and there is not. The only way to get a script other than via the HTML script tag, which has no way to specify a timeout, is XHR (short for XMLHttpRequest) or fetch where it is supported. Fetch only has an asynchronous interface, so it is no help. While it is possible to make a synchronous XHR request, according to MDN (the Mozilla Developer's Network), many browsers have deprecated synchronous XHR support on the main thread entirely. Even worse, XHR.timeout() "shouldn't be used for synchronous XMLHttpRequests requests used in a document environment or it will throw an InvalidAccessError exception."
So if you block the main thread to wait for the synchronous loading of the JavaScript, you have no way to abort the loading when you have decided it is taking too long. If you do not block the main thread, then the script will not execute until after the page has loaded.
Q.E.D
Partial Solution with Service Workers
#Kaiido argues this can be solved with ServiceWorkers. While I agree that ServiceWorkers were designed to solve problems like this, I disagree that they answer this question for a few reasons. Before I get into them, let me say that I think Kaiido's solution is fine in the much more general case of having a Single Page App that is completely hosted on HTTPS implement some kind of timeout for resources to prevent the whole app from locking up, and my criticisms of that solution are more about it being a reasonable answer to the interview question than any failing of the solution overall.
OP said the question came from "a long time ago" and service worker support in production releases of Edge and Safari is less than a year old. ServiceWorkers are still not considered "standard" and AbortController is still today not fully supported.
In order for this to work, Service Workers have to be installed and configured with a timeout prior to the page in question being loaded. Kaiido's solution loads a page to load the service workers and then redirects to the page with the slow JavaScript source. Although you can use clients.claim() to start the service workers on the page where they were loaded, they still will not start until after the page is loaded. (On the other hand, they only have to be loaded once and they can persist across browser shutdowns, so in practice it is not entirely unreasonable to assume that the service workers have been installed.)
Kaiido's implementation imposes the same timeout for all resources fetched. In order to apply only to the script URL in question, the service worker would need to be preloaded with the script URL before the page itself was fetched. Without some kind of whitelist or blacklist of URLs, the timeout would apply to loading the target page itself as well as loading the script source and every other asset on the page, synchronous or not. While of course it is reasonable in this Q&A environment to submit an example that is not production ready, the fact that to limit this to the one URL in question means that the URL needs to be separately hard coded into the service worker makes me uncomfortable with this as a solution.
ServiceWorkers only work on HTTPS content. I was wrong. While ServiceWorkers themselves have to be loaded via HTTPS, they are not limited to proxying HTTPS content.
That said, my thanks to Kaiido for providing a nice example of what a ServiceWorker can do.
1There is an excellent article from Jake Archibald that explains how asynchronous tasks are queued and executed by the window's event loop.
If the page doesn't reach the block that sets window.isDone = true, the page will be reloaded with a querystring telling it not to write the slow script tag to the page in the first place.
I'm using setTimeout(..., 0) to write the script tag to the page outside of the current script block, this does not make the loading asynchronous.
Note that the HTML is properly blocked by the slow script, then after 3 seconds the page reloads with the HTML appearing immediately.
This may not work inside of jsBin, but if you test it locally in a standalone HTML page, it will work.
<!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>
Here is a solution the uses window.stop(), query params, and 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>
You can use async property in script tag it will load your js file asynchronous.
script src="file.js" async>

What is causing my scripts to be requested twice while using Modernizr.load (yepnope.js)

I am using yepnope.js to load javascript files dynamically,and I've noticed that my scripts appear to be loaded twice according to Firebug and Webkit Inspector.
The problem is that in Firebug's Net panel (Firefox 4 latest), their response is a 200, not a 304. It seems to be slower than in Chrome.
I have uploaded this video showing the issue. You can see how the files jquery-1.6.1.min.js and libs.js are loaded an extra time.
The code I am using to do this is the following, simplified:
Modernizr.load({
load: ['jquery-1.6.1.min.js', 'libs.js'],
complete: function () {
console.log("loaded");
}
});
Modernizr.load() is yepnope().
There's a note about this in their documentation:
From http://yepnopejs.com/
I'm seeing two requests in my dev
tools, why is it loading everything
twice?
Depending on your browser and
your server this could mean a couple
different things. Due to the nature of
how yepnope works, there are two
requests made for every file. The
first request is to load the resource
into the cache and the second request
is to execute it (but since it's in
the cache, it should execute
immediately). Seeing two requests is
pretty normal as long as the second
request is cached. If you notice that
the second request isn't cached (and
your script load times are doubling),
then make sure you are sending the
correct cache headers to allow the
caching of your scripts. This is vital
to yepnope. It will not work without
proper caching enabled. We actually
test to make sure things aren't loaded
twice in our test suite, so if you
think we may have a bug in your
browser regarding double loading, we
encourage you to run the test suite to
see if the double loading test passes.

Explaining Google Analytics async tracker

I have a as to how google's async analytics tracker works. The following code is used to init a command array:
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(
['_setAccount', 'UA-xxxxxxxx-x'],
['_trackPageview']
);
</script>
Now, this is a standard array that gets replaced once the GA's code is loaded and is used as a sort of queue that stores your clicks.
My confusion lies in wondering how these clicks could possibly be persisted if a user clicks a link that causes a reload (prior to the GA javascript being loaded). If the GA code hasn't captured that push on the the _gaq object, then the user clicks a link and goes to a new page, this array is just re initialized each time no?
Isn't it true that a javascript variable will not persist across requests that cause a refresh? If this is the case, haven't we then lost that original click that caused the page reload?
Any explanation is greatly appreciated.
Yes, you're right that if the user clicks away from the site before ga.js has loaded and has executed the __utm.gif request to Google's servers, then it will not track the _gaq array and that information is gone forever. But this version code still provides many benefits over the older synchronous code.
First, the loading of ga.js using this method is not blocking.
Cleverly, the loading of ga.js is injected indirectly via JavaScript, rather than through a hard-coded <script> tag. As per Google Code Blog,
The second half of the snippet
provides the logic that loads the
tracking code in parallel with other
scripts on the page. It executes an
anonymous function that dynamically
creates a element and sets
the source with the proper protocol.
As a result, most browsers will load
the tracking code in parallel with
other scripts on the page, thus
reducing the web page load time.
This means that the loading of ga.js occurs in a non-blocking way for most modern browsers (and as a benefit, the async="true" part, currently supported in FF 4+, IE10p2+, Chrome 12+, Safari 5.1+, formalizes this asynchronization). This mildly reduces load time, and mildly reduces the likelihood that clicks will occur before ga.js has loaded.
The benefit of queuing up the _gaq array in advance is to prevent race conditions; priorly, if you tried to make GA calls before ga.js loaded (say, Event Tracking a video play), it would throw an error and the Event call would be lost and never recoverable. This way, as long as the ga.js eventually loads, the _gaq array is ready to serve it all of the calls at load time.
Yep. Javascript contexts are thrown away on page reload, so if the user leaves the page before ga.js loads, those hits are lost. The advantage of the async version of GA is that it can be put higher in the page, which means it's much more likely to have ga.js load before the user leaves.

External Javascript Timeout

I have a number of tracking scripts and web services installed on my website and I noticed when one of the services goes down, it still tries to call the external javascript file hosted on a different server. In Firefox, Chrome and other new browsers, there doesn't seem to be any issues when one of the services go down. However, in IE7 and IE8, my pages don't load all the way and time out before everything is displayed. Is there any way to add a time out on these javascript calls to prevent them from breaking my pages when they go down?
You can load them dynamically after page load with JS. If the JS files are on a different server, the browser will still show a "browser busy" indicator when you do that, but the original page will load.
If you can fetch the JS from your own site, you can load it with XMLHttpRequest after page load (or with your favorite JS library's helpers, e.g. jQuery's $.ajax(...)) and then eval it. This way the fetching itself won't show the browser-busy indicator.
To fetch the JS from your own site, you can download it from your tracking provider (which won't be officially supported but usually works) - just remember to refetch new versions every once in a while - or you can create a "forwarding" service on your own site that fetches it from the tracking provider and caches it locally for a while. This way your JS won't be in danger of staleness.
Steve Souders has more information about deferred loading of scripts and browser-busy indicators.
Try adding defer="defer"
The defer attribute gives a hint to
the browser that the script does not
create any content so the browser can
optionally defer interpreting the
script. This can improve performance
by delaying execution of scripts until
after the body content is parsed and
rendered.
Edit
This will prevent those scripts from running until the page loads:
function loadjs(filename) {
var fileref=document.createElement('script');
fileref.setAttribute("type","text/javascript");
fileref.setAttribute("src", filename);
}
window.onLoad = function() {
loadJs("http://path.to.js");
loadJs("http://path.to2.js");
...
}
If you need to load external scripts and you want to enforce a timeout limit, to avoid having a busy indicator running for too long, you can use setTimeout() with window.stop() and, the IE equivalent:
http://forums.devshed.com/html-programming-1/does-window-stop-work-in-ie-1311.html
var abort_load = function() {
if(navigator.appName == "Microsoft Internet Explorer") {
window.document.execCommand('Stop');
} else {
window.stop();
}
};
/**
* Ensure browser gives up trying to load JS after 3 seconds.
*/
setTimeout(abort_load, 3000);
Note that window.stop() is the equivalent of the user clicking the stop button on their browser. So typically you'd only want to call setTimeout() after page load, to ensure you don't interrupt the browser while it's still downloading images, css and so on.
This should be combined with the suggestions made by orip, namely to load the scripts dynamically, in order to avoid the worst case of a server that never responds, resulting in a "browser busy" indicator that's active until the browser's timeout (which is often over a minute). With window.stop() in a timer, you effectively specify how long the browser can try to load the script.
Also note that setTimeout()'s interval is not that precisely interpreted by browsers so round up in terms of how much time you want to allow to load a script.
Also, one counter-indication to using window.stop() is if your page does things like scroll to a certain position via js. You might be willing to live with that but in any case you can make the stop() conditional on NOT having already loaded the content you expected. For example if your external JS will define a variable foo, you could do:
var abort_load = function() {
if (typeof(foo) == "undefined") {
if(navigator.appName == "Microsoft Internet Explorer") {
window.document.execCommand('Stop');
} else {
window.stop();
}
}
};
This way, in the happy path case (scripts do load within timeout interval), you don't actually invoke window.stop().

How can I tell when a web page resource is cached?

Is there a way in JavaScript for me to tell whether a resource is already in the browser cache?
We're instrumenting a fraction of our client-side page views so that we can get better data on how quickly pages are loading for our users. The first time users arrive on our site, a number of resources (JS, CSS, images) are cached by the browser, so their initial pageview is going to be slower than subsequent ones.
Right now, that data is mixed together, so it's hard to tell an initial page load from a subsequent pageview that's slow for some other reason. I'd love a cross-browser way to check to see whether the cache is already primed, so that I can segregate the two sorts of pageview and analyze them separately.
You should use TransferSize:
window.performance.getEntriesByName("https://[resource-name].js")[0].transferSize
To verify it, you can run the above line on Chrome...
If the browser has caching enabled and your resource was previously loaded with proper cache-control header, transferSize should be 0.
If you disable caching (Network tab -> Disable cache) and reload, transferSize should be > 0.
There isn't a JavaScript API for checking if a resource is cached. I think the best you can do is check how long it took to load the resources, and bucket the ones with shorter load times together.
At the top of the page:
<script>var startPageLoad = new Date().getTime();</script>
On each resource:
<img src="foo.gif" onload="var fooLoadTime = startPageLoad - new Date().getTime()">
<script src="bar.js" onload="var barLoadTime = startPageLoad - new Date().getTime()">
When reporting load times:
var fooProbablyCached = fooLoadTime < 200; // Took < 200ms to load foo.gif
var barProbablyCached = barLoadTime < 200; // Took < 200ms to load bar.gif
You may need to use onreadystatechange events instead of onload in IE.
You need a plug-in to do this. Firebug can tell you on the "NET" tab, once you install it (for Firefox). JavaScript itself cannot see the browser's HTTP traffic.

Categories

Resources