Script async/defer/onload usage in Google's API javascript samples - javascript

In various javascript samples which Google provides for its API (e.g here), they use the following code to load the script from the html:
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
My understanding is that async/defer tell the browser when to load and execute the script and somewhat contradict each other. I have few questions:
What is the meaning of using both async and defer in this
context?
Why did Google choose to use this technique? Does it have any
performance or other benefits?
In the onload event, why do they first assign an empty function ( function(){}; ) to the event
before calling handleClientLoad()?
If I want to move the entire javascript to a separate js file,
what's the best approach to load both scripts? Since the new js file
will depend on api.js and can't be loaded asynchronously?
Thanks.

This is fairly well-covered by the WHAT-WG living standard for HTML's section on async and defer, which includes this handy graphic:
1. What is the meaning of using both async and defer in this context?
If the browser supports async, it ignores defer and does the async work. If not but it supports defer, it does the defer instead. If it doesn't support either, the script blocks DOM parsing, but all modern browsers support at least one.
2.Why did Google choose to use this technique? Does it have any performance or other benefits?
async fetches the script without blocking DOM parsing and rendering, and runs it as soon as it's available even if DOM parsing and rendering is still underway. defer will also avoid blocking DOM parsing and rendering, but won't run the script until parsing is complete (e.g., potentially later).
3. In the onload event, why do they first assign an empty function ( function(){}; ) to the event before calling handleClientLoad()?
This becomes clear if you look at onreadystatechanged: Basically it ensures that handleClientLoad is only called once by GAPI, not potentially twice (once by onload and once by onreadystatechanged.)
4. If I want to move the entire javascript to a separate js file, what's the best approach to load both scripts? Since the new js file will depend on api.js and can't be loaded asynchronously?
Well, it can be loaded asynchronously, you just have to handle the race condition with api.js. I'd probably:
Have handleClientLoad in an inline script above the script tag loading api.js, something like this:
var clientLoaded = false;
function handleClientLoad() {
if (!clientLoaded &&
typeof mainScriptLoad !== "undefined" &&
typeof gapi !== "undefined") {
clientLoaded = true;
mainScriptLoad();
}
}
Have mainScriptLoad in your separate file.
At the end of your separate file, call handleClientLoad.
That way:
If your script runs first, it'll call handleClientLoad but handleClientLoad will see that the GAPI isn't loaded yet and won't do anything; later, when the GAPI loads, it will call handleClientLoad and that will call mainScriptLoad because everything is ready.
If your script runs after GAPI loads, it'll call handleClientLoad but handleClientLoad will see that your main script isn't loaded yet and not try to call it. Later, when your script loads and calls handleClientLoad, handleClientLoad will call mainScriptLoad because everything is ready.

Related

Will setting defer on my polyfill and other scripts guarantee that they're loaded in order?

I'm using polyfill.io to polyfill Promise and fetch for older clients. On their website they recommend using a script loader or their callback to make sure the script has loaded completely before running the modern code:
We recommend the use of the async and defer attributes on
tags that load from the polyfill service, but loading from us in a
non-blocking way means you can't know for certain whether your own
code will execute before or after the polyfills are done loading.
To make sure the polyfills are present before you try to run your own
code, you can attach an onload handler to the https://cdn.polyfill.io
script tag, use a more sophisticated script loader or simply use our
callback argument to evaluate a global callback when the polyfills are
loaded:
However, shouldn't setting defer on both scripts already guarantee that they are loaded async but still in the order in which they appear in the document (unless the browser doesn't support defer)?
<script src="https://cdn.polyfill.io/v2/polyfill.min.js" defer></script>
<script src="modernscript.js" defer></script>
According to MDN documentation defer attribute just defines a point of page loading time when script loading will occur.
From documentation that you've citated it can be seen:
To make sure the polyfills are present before you try to run your own
code, you can attach an onload handler to the https://cdn.polyfill.io
script tag
Since (as pointed into comments to this answer) it can't be clearly seen if defer scripts will be executed (1, 2) and taking in mind possible browser implementation differences - it may be not the best idea to rely on such behavior.
So better way would be either:
to use some script loader (RequireJS for example)
to add proposed onload handler to first <script> tag and create dynamic <script> tag for loading your code inside this handler
to bundle your code together with Promise polyfill (manually or using bundler like webpack) and load as single bundle.
UPDATE: As pointed by #PeterHerdenborg in comment - MDN document now clearly states that:
Scripts with the defer attribute will execute in the order in which they appear in the document.

"Load" event on script with async and/or defer

When embedding scripts like:
<script src="..." async defer></script>
Is there a way to know when they're finished loading?
Usually when the window.load event is called, one would expect all scripts to be ready as well. But I don't know if that still holds when you load them with async or defer. I've read some docs online but couldn't find anything conclusive on this issue.
Answer:
You could take advantage of the onload event attribute in order to perform some kind of callback once your script is loaded.
Example: In the example html script element below when the script (jquery library from google api) finishes loading asynchronously, an alert will pop up saying 'resource loaded'.
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js" async defer onload="alert('resource loaded');">
Note: The src script will load very fast because it is hosted by google so the pop up will most likely appear as soon as the page/DOM has loaded.
Edit: added important information originally from comment.
window.onload waits for everything to load before firing whereas document.onload fires when the Document Object Model (DOM) is ready.
So if you've got async scripts document.onload will execute first while window.onload will wait for those asynchronous scripts to finish loading.
To summarize:
window.onload will take async scripts into account.
document.onload will not take async scripts into account.
Emphasis mine:
Is there a way to know when they're finished loading?
Usually when the window.load event is called, one would expect all scripts to be ready as well. But I don't know if that still holds when you load them with async or defer. I've read some docs online but couldn't find anything conclusive on this issue.
Addressing the points in bold (for specific single scripts you can use their onload events), the TL;DR is:
The document.DOMContentLoaded event will happen after all normal and deferred scripts load and execute, but doesn't care about async scripts.
The window.load event will happen after all normal, async, and deferred scripts load and execute.
Note: A script that has both async and deferred set will act as deferred on legacy browsers that don't support async, and will act as async otherwise. So the safe bet is to think of them as async.
The HTML specification does say this, albeit indirectly. The spec defines three distinct script collections that every document has (I'm naming them S1, S2, and S3):
Each Document has a set of scripts that will execute as soon as possible, which is a set of script elements, initially empty. [S1]
Each Document has a list of scripts that will execute in order as soon as possible, which is a list of script elements, initially empty. [S2]
Each Document has a list of scripts that will execute when the document has finished parsing, which is a list of script elements, initially empty. [S3]
Just above that, in the section about preparing script elements, it details how scripts are distributed to those collections. Generally speaking, during load:
These are placed in S1 (see step 31.2.2):
All async external (scripts with a src) scripts.
All async module (depends on type attribute) scripts.
These are placed in S2 (defer is irrelevant for these) (see step 31.3.2):
Non-async, injected (e.g. by the browser) external scripts.
Non-async, injected module scripts.
These are placed in S3 (see step 31.4):
Deferred, non-async, non-injected external scripts.
All non-async, non-injected module scripts (defer is irrelevant for these).
These are executed synchronously and aren't placed in any of the collections:
Non-deferred, non-async, non-injected external scripts (see step 31.5).
All inline (without a src) scripts (neither async nor defer apply to these) (see step 32).
In simplified terms:
S1 contains all the async external/module scripts.
S2 contains all the non-async injected external/module scripts.
S3 contains all the deferred external/module scripts.
Inline scripts and vanilla external scripts are executed as they're loaded and parsed (as part of the parsing operation).
The HTML spec then goes on to define what happens after parsing is complete, where the relevant parts are, in order:
Change document's ready state to "interactive"; fires document.readystatechange (see step 3)
Execute all scripts in S3 (deferred non-async non-injected) (see step 5)
Queued (will happen >= now): Fire a DOMContentLoaded event on document (see step 6.2)
Wait until all scripts in S1 (async) and S2 (non-async injected) have been executed (see step 7)
Wait until any other load-blocking operations have been completed (see step 8)
Queued:
Change document's ready state to "complete"; fires document.readystatechange (see step 9.1)
Fire a load event on window (see step 9.5)
Fire a pageshow event on window (see step 9.11)
If the document is in some container (e.g. an iframe), fire a load event on the container (see link in step 9.12)
In simplified terms, the events that depend on script executions are:
document.DOMContentLoaded happens after all the deferred scripts are executed.
document.readystatechange ("complete") and window.load happen after all scripts are executed.
window.pageshow also happens after all scripts are executed, although it happens at other times later, too.
If there's a container like an iframe or something, its load event happens after all scripts are executed as well.
Btw, as for scripts with both async and defer set, the part describing these attributes says:
The defer attribute may be specified even if the async attribute is specified, to cause legacy web browsers that only support defer (and not async) to fall back to the defer behavior instead of the blocking behavior that is the default.
For "modern" browsers, I assume the behavior when both are specified is to just adhere to the logic above, i.e. those scripts end up in S1 and defer is essentially ignored.
So uh... yup.

why Script DOM Element can be async loading

(function() {
var s = document.createElement('script');
s.type = 'text/javascript';
s.src= 'http://yourdomain.com/script.js';
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
})();
I have some js codes,i don't know why it can be async loaded? I add script dom element to html,why this script can be loaded async but other script and html loaded in one thread.
It sounds to me that you might be confused by what the "async" loading does.
All HTML and scripts (including dynamically inserted scripts like in your case) parse and execute in a single thread.
However, fetching (meaning downloading from the web) can be done at the same time as the above thread. The browser will continue fetching external files (CSS, scripts, images, etc.) in parallel. But in the case of scripts, it will pause parsing when it reaches a script tag that is not fully fetched yet, and execute the script once fetched, so that the script has a chance to modify the rest of the page before the browser parses it. You can change that behaviour using async and defer attributes in your script tag. The just described behaviour corresponds to the default values async = false and defer = false.
When async == true, the browser will continue parsing the page while fetching the script in parallel, then pause the parsing when fetch is completed (hence this can happen at any point in parsing the page), and execute the script. Here you can see the effect of the single thread. I think the spec has a good figure to illustrate that.
Now in the case of dynamically inserted scripts like in your case, most browsers will use async = true by default. Refer to that post and MDN notes for further details.
Finally, there is another meaning when referring to "asynchronous" loading: AMD (if you use Require.js for example) typically uses the code that you show to insert script tags dynamically. In this context, the asynchronous is more about the ability to fetch and execute different scripts "on demand", i.e. only when the user requests some extra functionalities (for example you open a form edition, so the page will only fetch and execute the script related to editing at that moment).

Make sure my .JS file loads every time before others

I have a website where I don't have access to the source but I can manipulate it using Javascript. I have a file called main.js that has been included at the very end of the includes to which I have the access to and I would like to run my custom Javascript code in that file. I have a .JS file with a function called helloWorld() on my server that I would like to load before any $(document).ready() callback fires, because one of the $(document).ready() functions on my website page/pages uses this function.
Custom .JS file:
function helloWorld()
{
alert("Hello World");
}
main.js file on the server (Accessible):
//All the JS code that the website uses
..
// My custom javascript code that includes my custom .JS file
$.getScript("helloWorld.js", function()
{
// Use anything defined in the loaded script...
});
Now I would like the helloWorld() to be loaded whilst the page is loading and before any $(document).ready() functions fired. I understand that loading this .JS file while the page is loading will possibly slow down the page load. Is there a bullet-proof way of making sure that my custom javascript function will be loaded prior to any $(document).ready()'s? If there is any other way I can achieve this, please do let me know. Looking forward to your suggestions.
Looks like I found a solution for your problem. I wouldn't suggest it, but it's the only way you can load an external script from another one before the DOMContentLoaded event fires.
Solution
Since that your main.js script is in the <head> of your document, you can be sure that it will be loaded and executed before any following part of the DOM. Given this, you can use a synchronous XMLHttpRequest to load your script and execute it.
This kind of technique has some pros and cons:
Pros: you can load and execute any script before DOMContentLoaded, and also multiple scripts sequentially.
Cons: your document will be frozen until the requests are completed.
Not that bad, if your script isn't enormous it will not drastically impact the loading time. We can still do it.
Implementation
First of all, make sure that your custom.js script is served over a link which will always be reachable, so that your request will not fail. Also make sure that your main.js script hasn't got async or defer properties, so that it will always be executed in the <head>, before the DOMContentLoaded event.
<!-- NOT GOOD -->
<script src="main.js" defer></script>
<script src="main.js" async></script>
<!-- GOOD :) -->
<script src="main.js"></script>
Now that you're ready, in your main.js script you'll need to:
Create and initialize a synchronous XMLHttpRequest object, and send() a GET request to your content.js script.
Create a <script> element, and put the result of your request (which is stored in the .responseText property of the request object) inside it.
Append the script to the <head> to make it run before the DOM is loaded.
Plus, if you also want your script to be removed right after execution (so it will not be visible to the users), you can:
Remove the <script> from the document after it has ran its code. You'll need to listen for the onload event for this.
Also, if you want to make your code run completely anonymously, you can wrap it inside an anonymous function to prevent the access to it from the global scope.
Here's a quick example of what you'll need to do in your main.js file:
(function() {
// Create the request and the script
var xhr = new XMLHttpRequest(),
s = document.createElement('script');
// Send the request to retrieve custom.js
xhr.open('GET', 'path/to/custom.js', false);
xhr.send();
// Listen for onload, and remove the script after execution
s.addEventListener("load", function(e) {
s.parentElement.removeChild(s);
});
// Load the code inside the script and run it in the head
s.textContent = xhr.responseText;
document.head.appendChild(s);
})();
Now your custom.js script will (anonymously) run before DOMContentLoaded, mission complete!
As far as I can see, there are multiple ways of doing this, but the best way would be to use something like Require.js or CommonJS to resolve your dependencies, concat them, and and publish the resulting concatenated javascript file (or many if you can divide your app into multiple sections).
The not-so-great method would be to use the main script to load other scripts by adding script tags, this way you can ensure its there since its the one loading the other scripts.

load scripts asynchronosly and have a fallback

My objective here is to load scripts asynchronously when the browser supports defer or async.
If the browser supports neither I don't care about asynchronous loading (not my bad).
I want to make sure that any script is only executed when the prerequisites for it are fulfilled e.g. jQuery loaded.
I want to load my scripts while other scripts are being loaded (only if the browser supports defer or async).
I want to do this using only the browsers' API. I don't want the browser to load any reliable (or not) scripts that do that for me no matter how small they are.
This must work with IE8+, Gecko v.1.9.1+ (e.g. firefox 3.5.* or firefox 9.0+), webkit (e.g. chrome), presto (e.g. Opera). For the ones I didn't mention the version, I mean the latest stable version.
If possible I don't want any non easy scripts. I just need something simple to do this job. This means:
If possible, I don't want stuff like AJAX calls or fancy Objects with some methods to do some workarounds like I've seen in other pages. Those are to force async loading of the script in browsers that do not support async or defer
I repeat: I don't need any fancy things to make a script asynchronous. If the browser does not support defer or async I don't care. I just care is that the script is loaded so that each part is executed after its prerequisites are met and use async or defer if the browser supports it.
First, using a library such as jQuery makes this whole process infinitely easier, and reliable across browsers. It may increase the download size of your pages (by a very small amount) but the speed gained by efficient script loading/executing will nearly always outweigh that.
Regarding script async and defer attributes:
async="async": on a script tag is not supported at all by IE8/9, script executes immediately (which is ok according to your question).
defer="defer": on a script tag will begin loading after everything in the order the defer scripts appear in the HTML, prior to DOM Ready. BUT, on Firefox, scripts will often execute AFTER dom ready. This difference makes defer unreliable as a means of ensuring that scripts are loaded before executing functions after dom ready.
General Guidelines when not using jQuery:
If a script has downstream dependancies you have to place it as a standard script tag at the end of the body tag and have your inline tags all execute after document ready. Otherwise there is no guarantee that the script will be executed prior to the execution of the dependencies. Firefox is the main issue here, a "defer" script may not have finished even after DOM ready.
If a script has no downstream dependnacies, then place it at the end of the body tag, and use async="async" attribute on the script tag. IE will render it immediately and the others will render it when they receive it.
General Guidelines when using jQuery:
Place only jQuery in your <head>.
Execute all other scripts as $.getScript().
If a script needs to execute ASAP (such as analytics) use a $.getScript at the top of the body (this will be a non-blocking request, but will process as soon as the client receives the file).
If a script can wait till DOM ready, wrap the $.getScript() call in $(function() {});
If a script has many downstream dependancies, have each one register itself to the callback function for a specific script.
$(function() {
$.getScript("script.js", function() {
for(var i = 0; i < myCallbacks.length;i++) {
myCallbacks[i]();
}
});
});

Categories