If you use plain script tags on an HTML page, rendering is blocked until the script has been downloaded and parsed. To avoid that, for faster page display, you can add the 'async' attribute, which tells the browser to continue processing down the page without waiting for that script. However, that inherently means that other javascript that refers to anything in that script will probably crash, because the objects it requires don't exist yet.
As far as I know, there's no allScriptsLoaded event you can tie into, so I'm looking for ways to simulate one.
I'm aware of the following strategies to defer running other code until an async script is available:
For a single script, use their 'onload' event or attribute. However, there's no built-in way I know of to tell when ALL scripts have loaded if there's more than one.
Run all dependent code in onload event handlers attached to the window. However, those wait for all images too, not just all scripts, so the run later than would be ideal.
Use a loader library to load all scripts; those typically provide for a callback to run when everything has loaded. Downside (besides needing a library to do this, which has to load early), is that all code has to wrapped in a (typically anonymous) function that you pass into the loader library. That's as opposed to just creating a function that runs when my mythical allScriptsLoaded fires.
Am I missing something, or is that the state of the art?
The best you could hope for would be to know if there are any outstanding async calls (XMLHttpRequest, setTimeout, setInterval, SetImmediate, process.nextTick, Promise), and wait for there to not be one. However, that is an implementation detail that is lost to the underlying native code--javascript only has its own event loop, and async calls are passed off to the native code, if I understand it correctly. On top of that, you don't have access to the event loop. You can only insert, you can't read or control flow (unless you're in io.js and feeling frisky).
The way to simulate one would be to track your script calls yourself, and call after all script are complete. (i.e., track every time you insert a relevant script into the event loop.)
But yeah, the DOM doesn't provide a NoAsyncPending global or something, which is what you'd really require.
Related
I'm a NodeJs developer that happens to have some web applications.
I'm very familiar with asynchronous code and how the event loop works. However I been facing a problem that I was unable to fix until I realized that asynchronous code may behave different when it is splitted across several script tags.
The situation was as follows
I had one script tag at the head section with some asynchronous code. Such code expected some function to exist. That function should be declared on the second script tag in a synchronous fashion, so at the time the async stuff completes that function should exist.
Te second script tag is 100% synchronous and at the middle of the code it created a function called boot.
Let me ilustrate it with some simplified code:
// <script>
somePromise()
.then( ()=> window.boot())
// </script>
// < ... some html code and body >
// <script>
window.boot = function (){}
// </script>
Since the promise callback should be executed asynchronously I expected it to go to the event loop and allow the rest of the synchronous code to execute.
On some browsers, this worked as I expect. When I say some browsers I mean browsers of different users on different computers, because the behavior varies even using the same browser brand and version. However, there were situations when the promise callback was executed before the second second script tag had a chance to start and raising an error.
How is this supposed to work on browsers ? Is each script tag executed until all its code is executed, even the asynchronous one ? If that is the case, why does it work for some other browsers?
Thanks in advance.
The HTML5 parser is not synchronous.
This is my own reworded extract of parts of HTML Parser Threading on MDN. Please refer to the original for formal treatment and heavy reading.
HTML between the script tag [pairs] is parsed by a nsHtml5StreamParser which executes off the main thread.
The parser runs its own event loop, and responds to requests from the main thread to perform onDataAvailable and OnStopRequest processing. Processing calls DoDataAvailable and doStopRequst respectively.
DoDataAvailable outputs small DOM tree operations, such as element creation into a queue processed in the main thread. The tree op queue is flushed to the main thread from time to time ** using a timer.**
After flushing the parser dispatches a runnable to the main thread to process tree additions. The runner (or executor) in the main thread calls nsHtml5TreeOpExecutor::RunFlushLoop().
A comment from #KarelG in this question says that network data is usually processed in 8Kb chunks. The question is well worth reading.
So the JavaScript Event Loop sometimes gets an opportunity to fulfill a promise and execute an onFulfilled handler before the second script element is parsed and executed - as you already discovered!
In summary it apppear that the vagaries of network retrieval of source code, asynchronous HTML parsing that uses a timer to instigate processing of tree building operations, and any further asynchronous operations that the tree builder may or may not invoke, all combine together to create a race condition.
The unpredictability of the failure branch of the race condition, when window.boot is not defined when called, is most likely due to the combined effects of browser brand, network speed, device utilization, length and type of HTML content, and HTML parser timing and timers.
The obvious conclusion is that code should not set up this kind of race condition in the first place. It is unsafe coding practice. Thankfully you can work around it.
Actually, promise is not suported in IE, so what you could do is to use some external libs, like bluebirdJS, or you could use a transpiler (babel or another one)
I'm loading some javascript code via ajax (as a <script> block inside an html string), and I'm using jquery.html() to inject that payload into a <div>. The jquery.html() method automatically evals injected <script> elements.
I would like to wait for the code in the <script> block to complete execution before releasing execution back to the main event loop. Even if the <script> contains callbacks, like an $.ajax().done() or setInterval(). Even if it causes an infinite loop, and even if it blocks any other events from being processed for a very long time.
Is this possible? I'm just wondering if there's some kind of synchronization-wrapper browser-supported utility in Javascript. My hunch is no, since this seems contrary to the single event-loop nature of Javascript, but I thought I'd check :) Ultimately, my goal is to insert custom javascript into the page upon a specific user action, and guarantee that the code will be executed in its entirety.
Note that I don't need to use jquery.html() or <script> injection, that's just my starting point. Maybe the best I can do is tell the client to invoke a specific pre-defined callback function when their code is finished, and make sure I don't change the window until this function is invoked?
You cannot "wait for the code in the block to complete execution before releasing execution back to the main event loop" in Javascript. That simply cannot be done.
Done right, you could insert the script tags with onload handlers so you would be notified (via a callback) when those scripts had finished loading and finished their synchronous execution (but not their async execution).
If you wanted to know when async execution in those scripts would be done, you would have to build something into those scripts to notify you when they were done.
If you're going to use jQuery's .html() to do script tag injection you lose a lot of control over this process and it becomes a hacking project to try to figure out what you can do from outside the process.
In all cases, you cannot prevent the continuing execution of the main event loop of javascript while waiting for this all to finish.
Is it possible to execute setTimeout() or setInterval() synchronously so that further execution that depends on its callback will not cause a not defined error?
intv = window.setInterval(function() {
// do some stuff
doSomeStuff();
// kill interval when stuff is done
if (stuffIsDone)
window.clearInterval(intv);
}, 10);
// dependent on "stuff" being done
// I want this to execute only after intv is cleared
doMoreStuff();
I don't want to put every consecutive call inside of a timeout to check if (typeof someStuff != 'undefined')
Yes, I do understand that this will cause a delay in loading and possible the UI. The intervals will be extremely small and inconsequential.
EDIT... Alright, what I'm ultimately trying to do is dynamically add a number of javascript files dynamically, by only including a single javascript file.
ie.:
// auto include all javascript files
<script language="javascript" type="text/javascript" src="include.js"></script>
This works by requesting the JSON list of javascript files from the server via AJAX. It loops through the list and adds the scripts dynamically to the DOM.
The catch:
If I add the scripts using setInterval, they are sometimes added after the onLoad event fires, depending on the current computational load of the machine executing the code. So, when I call functions from one of the files onLoad, it causes an error (because at the time of execution, the function didn't exist)
If I add the scripts inside of a while loop, the dynamically added scripts to do not execute and the internal references between the scripts are invalid and error out.
So, the question really is: without using setInterval and typeof on every call, how do I dynamically add scripts reliably so that dependent code doesn't attempt to execute before the depended-upon scripts are loaded?
Take a look at Refactoring setInterval-based Polling and consider converting your setInterval to a setTimeout and use a Promise to execute doMoreStuff() after stuffIsDone is true.
That article is from 2013, so consider Promises/A+ (using a polyfill for ES6-style Promises for older browsers) instead of jQuery's or Underscore's implementation.
You can run a while loop and keep track of the system time if you want to block the JavaScript thread for a fixed period of time.
For example:
var startMillis = Date.now();
while (Date.now() - startMillis < 10);
Note that no other code will run during this period. JavaScript is single threaded.
Don't know exactly what you're trying to do, but this seems a little strange :)
When writing user scripts for various websites I often come across the problem of dealing with elements or JS functions that don't exist yet at the time of execution. I'd like to know how to get to those functions or elements.
An obvious solution is a setTimeout, but that's very inelegant. I've also experimented with the various events like DOMContentLoaded and window.onload, but I often find that these events are still too early, due to content and code being dynamically generated/loaded afterwards.
Make sure that whatever you're loading emits an event, that you can hook into.
If you're loading a script file you can hook into the onload event. If you do an AJAX call you can hook into the onreadystatechange event.
These are some useful native events.
You can also make more custom events by using Promises.
var modules = {/* */};
var foo = modules.load("foo"); // returns a promise
foo.done(function (foo_module) {
// we now have the foo-module
});
requirejs might be worthwhile to look at, just to see how they do things.
As for promises, this is a good read: What’s so great about JavaScript Promises?
Make sure you're not doing something wrong before jumping to conclusions. Like, remember that user scripts in Chrome for example, are sandboxed, and you'll need to jump through a hoop or two to even access code on the target page. For small scripts, I suggest the "Location Hack". https://stackoverflow.com/a/5006952/125938
So I've been using require.js for while now, but I realized that I actually don't know how it works under the hood. It says that it's an AMD loader.
I do understand that CommonJS is synchronous, which means that it blocks execution of other codes while it's being loaded. On the other hand, AMD is asynchronous. This is where I get confused.
When I define a module, it has to load a,b,c in order to execute the callback. How does asynchronous work here?
Isn't it synchronous when it has to load those three dependencies first?
Does it mean that AMD loads a,b,c asynchronously then checks to see if those files are loaded (doesn't care about the order) then execute the callback?
define("name",["a","b","c"], function(a,b,c){
});
As you know, "AMD" (Asynchronous Module Definition (AMD)) is a specific API. There are many AMD-compatible "loaders", including RequireJS, curl.js and Dojo (among others).
Just as frameworks like JQuery and Dojo give you an API over raw Javascript; a program that uses AMD:
1) requires you an AMD-compatible .js library,
2) demands certain programming "rules" and "conventions", and
3) Ultimately sits "on top" of Javascript, which runs on your "Javascript engine" (be it IE, Chrome, Firefox - whatever).
Here are a couple of links I found useful:
https://www.ibm.com/developerworks/mydeveloperworks/blogs/94e7fded-7162-445e-8ceb-97a2140866a9/entry/loading_jquery_with_dojo_1_7_amd_loader2?lang=en
http://dojotoolkit.org/reference-guide/1.8/loader/amd.html
http://blog.millermedeiros.com/amd-is-better-for-the-web-than-commonjs-modules/
http://addyosmani.com/writing-modular-js/
PS:
To answer your immediate question, the latter link has a bit of discussion about "require()" and "dynamically_loaded dependencies".
Since I wrote an AMD loader, I'll try to answer the questions directly:
Isn't it synchronous when it has to load those three dependencies first?
Javascript, by definition, is single threaded. That means that anything you run in it always runs sequentially. The only thing that you can do in a browser is include scripts using the "async" parameter on the script tag, which will make the order in which scripts are being loaded undefined (asynchronous). Once a script executes it will be the only one executing at that point in time.
Does it mean that AMD loads a,b,c asynchronously then checks to see if those files are loaded (doesn't care about the order) then execute the callback?
Correct. AMD-define() allows you to load all scripts in any order you wish (i.e. ultimately you let the browser roll the dice and load them in any order it sees fit at any time it sees fit).
Then any time a define() is called, the AMD-loader will check if the current list of dependencies for this define has already been satisfied. If it is, it will call the current callback immediately, and after that, it will check if any of the previously registered define-callbacks can be called too (because all of their dependencies have been satisfied). If the dependencies for this callback have not all been satisfied yet, the callback is added to the queue to be resolved later.
This eventually results in all callbacks being called in the correct dependency order, regardless of the order in which the scripts have been loaded/executed in the first place.