Help - async/defer javascript ordering


A

axlq

OK, I'm writing a web app in which I'd like to display a "busy"
graphic while my scripts load. That's no problem. The page content
displays the graphic while a function triggered by window.onload
loads my scripts. That part works.

My problem is that when one script depends on another being loaded
first, I can't seem to control the ordering in which they load, and
I get an error.

This is HTML5, so the sync and defer attributes should be working.

Bare bones example, in which I load the Google Maps API followed by a
script that uses it, after the main body of the document is loaded:

=================================================================

<!DOCTYPE html>
<html>
<head>
<title>Whatever</title>
<script>
function loadscript(url,sync,onload) { // load a javascript
var s;
s = document.createElement('script');
s.type = 'text/javascript';
s.src = url;
if (onload) s.onload = onload;
if (sync) s.async = 'async'; else s.defer = 'defer';
document.getElementsByTagName('head')[0].appendChild(s);
}

function loadstuff() {
loadscript('http://maps.google.com/maps/api/js?sensor=false',false);
loadscript('lib/mapops.js','js',false); // needs google maps
// ...etc...
}
if (window.addEventListener) window.addEventListener("load",loadstuff,false);
else if (window.attachEvent) window.attachEvent("onload",loadstuff);
</script>
</head>
<body>
body content here
</body>
</html

=================================================================

The above example generates
"Uncaught TypeError: undefined is not a function"
at this line in mapops.js:

var geocoder = new google.maps.Geocoder();

....which requires the Google Maps script to be loaded first. And it
should be loaded first. The above code sets async to false and defer to
true, so the scripts should be loaded in order.

If I load the scripts the traditional way, using <script> tags in the
document head, it works fine. But then I don't accomplish the effect
with the "busy while loading" graphic in the body.

Any suggestions or hints? I'm an experienced programmer, but
javascripts weird asynchronous behavior is bedeviling me.

Thanks.
-Alex
 
Ad

Advertisements

J

J.R.

OK, I'm writing a web app in which I'd like to display a "busy"
graphic while my scripts load. That's no problem. The page content
displays the graphic while a function triggered by window.onload
loads my scripts. That part works.

My problem is that when one script depends on another being loaded
first, I can't seem to control the ordering in which they load, and
I get an error.

Some 'JavaScript libraries' use to create an array in order to store the
functions to be called by the onload event. But it's not guaranteed that
all browsers will load these functions using the array order...
This is HTML5, so the sync and defer attributes should be working.

Bare bones example, in which I load the Google Maps API followed by a
script that uses it, after the main body of the document is loaded:

=================================================================

<!DOCTYPE html>
<html>
<head>
<title>Whatever</title>
<script>
function loadscript(url,sync,onload) { // load a javascript
var s;
s = document.createElement('script');
s.type = 'text/javascript';
s.src = url;
if (onload) s.onload = onload;
if (sync) s.async = 'async'; else s.defer = 'defer';
document.getElementsByTagName('head')[0].appendChild(s);
}

function loadstuff() {
loadscript('http://maps.google.com/maps/api/js?sensor=false',false);
loadscript('lib/mapops.js','js',false); // needs google maps
// ...etc...
}

I'd suggest putting the loadscript() function in the end of the page,
just before the closing said:
if (window.addEventListener) window.addEventListener("load",loadstuff,false);
else if (window.attachEvent) window.attachEvent("onload",loadstuff);

Use just simply in the end of the page:

window.onload = function () {
loadscript('http://maps.google.com/maps/api/js?sensor=false',false);
loadscript('lib/mapops.js','js',false); // needs google maps
// ...etc...
}

If I load the scripts the traditional way, using<script> tags in the
document head, it works fine. But then I don't accomplish the effect
with the "busy while loading" graphic in the body.

Any suggestions or hints? I'm an experienced programmer, but
javascripts weird asynchronous behavior is bedeviling me.

I'd suggest reading up on the window.onload problem:
<http://peter.michaux.ca/articles/the-window-onload-problem-still>
 
T

Thomas 'PointedEars' Lahn

axlq said:
<!DOCTYPE html>
<html>
<head>
<title>Whatever</title>
<script>
function loadscript(url,sync,onload) { // load a javascript
var s;
s = document.createElement('script');
s.type = 'text/javascript';
s.src = url;
if (onload) s.onload = onload;
if (sync) s.async = 'async'; else s.defer = 'defer'; ^^^^^^^
document.getElementsByTagName('head')[0].appendChild(s);
}

function loadstuff() {
loadscript('http://maps.google.com/maps/api/js?sensor=false',false);
loadscript('lib/mapops.js','js',false); // needs google maps

You are passing the wrong arguments here. This only works because of a
suitable setter, nothing reliable.
// ...etc...
}
if (window.addEventListener)

This is hardly a reliable feature test. Read the FAQ Notes.
window.addEventListener("load",loadstuff,false); else if
(window.attachEvent) window.attachEvent("onload",loadstuff); </script>

JFYI: window.addEventListener() and window.attachEvent() are _not_
equivalent. The first usually obeys order, the second does not.

You could go without all that unreliable nonsense if you simply used the
`onload' attribute of the `body' element. Contrary to popular belief spread
by "Unobtrusive JavaScript" lemmings, it is not proprietary or less
reliable.
[…]
The above example generates
"Uncaught TypeError: undefined is not a function"
at this line in mapops.js:

var geocoder = new google.maps.Geocoder();

...which requires the Google Maps script to be loaded first. And it
should be loaded first. The above code sets async to false and defer to
true, […]

No, evidently it doesn't. It sets the property to "async", a non-empty
string, which ought to be converted to `true' by an implicit setter if
`async' is implemented as a boolean property.

The only remotely reliable way to load scripts in order is to have each
script load its dependencies. Still I fail to see the advantage in loading
scripts synchronously with scripting instead of simply putting `script'
elements in the required order in the HTML document, which would do just
that.


PointedEars
 
A

axlq

loadscript('http://maps.google.com/maps/api/js?sensor=false',false);

You are passing the wrong arguments here. This only works because of a
suitable setter, nothing reliable.

Aargh. No, I'm not. That argument is an artifact of me trying to
simplify code for posting. My original loadscript function distinguishes
between javascript and css, hence the 'js' argument. Sorry about that.
This is hardly a reliable feature test. Read the FAQ Notes.

FAQ notes? The FAQ for this newsgroup or some other FAQ?
The only remotely reliable way to load scripts in order is to have each
script load its dependencies. Still I fail to see the advantage in loading
scripts synchronously with scripting instead of simply putting `script'
elements in the required order in the HTML document, which would do just
that.

As I explained in my initial post, the reason is to have a busy-wait
graphic displaying while things load. That requires delaying as much
as possible until the page body with the graphic can be loaded. This
is a web app, so the wrapper for it on mobile devices won't have any
browser indications like a progress bar, address bar, etc.

In any case, after experimenting further, it seems that one can't treat
the Google Maps API the way I've been trying to.

-A
 
T

Thomas 'PointedEars' Lahn

axlq said:
FAQ notes? The FAQ for this newsgroup or some other FAQ?

This newsgroup (of course).
As I explained in my initial post, the reason is to have a busy-wait
graphic displaying while things load. That requires delaying as much
as possible until the page body with the graphic can be loaded.

But does it make sense to have the user wait for the document to be loaded,
and then again for the scripts?
This is a web app, so the wrapper for it on mobile devices won't have any
browser indications like a progress bar, address bar, etc.

Pardon? Yes, it would, and it should.
In any case, after experimenting further, it seems that one can't treat
the Google Maps API the way I've been trying to.

I do not see why not (technically), but YMMV.


PointedEars
 
A

axlq

But does it make sense to have the user wait for the document to be loaded,
and then again for the scripts?

Well, what makes sense is to have some indicator that tells the
user "please wait, I'm loading the app". That's what I'm trying to
accomplish here.

Ideally, the "document" would consist of nothing but the busy-wait
graphic, which gets displayed while *all* the rest of the content,
including javascripts, get loaded. The final javascript act would
be to initialize what's needed, remove the busy-wait div from the
document and show the content.

Right now the body content includes everything, because I still need
to organize the PHP scripting on the server end before I split the
PHP scripts into head and body.
Pardon? Yes, it would, and it should.

No, it wouldn't, and shouldn't. This is pretty standard, actually,
when developing web-based apps for mobile devices. The idea is
that an app should appear as an app, not a web site, and it's the
responsibility of the app to provide the necessary controls and
indications. The PhoneGap cross-platform mobile device API works
this way (your apps are written in HTML and javascript). The new
Meego OS to replace Symbian in Intel-based Nokia devices includes
such a browser.

I could probably accomplish what I want more easily by dragging in a
framework like jQuery, but I want to avoid the bloat of increasing
the size of my app, and the user's data plan usage, by a factor of
two.
I do not see why not (technically), but YMMV.

I think it may have something to do with loading scripts after the
window.onload event has already fired, so the scripts loaded after
that won't initialize properly if they rely on that onload event. I
can handle that situation in my own scripts, but not third-party
ones.

-A
 
Ad

Advertisements

T

Thomas 'PointedEars' Lahn

axlq said:
Well, what makes sense is to have some indicator that tells the
user "please wait, I'm loading the app".

That depends on what the app is. If it is a Web app, it does not make sense
as you approach it.
That's what I'm trying to accomplish here.

Ideally, the "document" would consist of nothing but the busy-wait
graphic, which gets displayed while *all* the rest of the content,
including javascripts, get loaded. The final javascript act would
be to initialize what's needed, remove the busy-wait div from the
document and show the content.

That is not an ideal, it would be an accessibility nightmare. Robots could
not index anything, too.
Right now the body content includes everything, because I still need
to organize the PHP scripting on the server end before I split the
PHP scripts into head and body.
Good.


No, it wouldn't, and shouldn't. This is pretty standard, actually,
when developing web-based apps for mobile devices. The idea is
that an app should appear as an app, not a web site,

I am using an iPhone, and I have used an Android-based device for several
months. I can tell you that all Web apps I have used so far have not been
displayed differently than other Web sites with regard to the browser
chrome. That is good so because you want to be able to navigate away from
the Web app.
and it's the responsibility of the app to provide the necessary controls
and indications.

A Web app usually provides its own controls, in the operating systems or
browser's UI style, but it does not (and AFAIK cannot) disable the built-in
controls of the browser it is displayed in.
The PhoneGap cross-platform mobile device API works this way (your apps
are written in HTML and javascript). The new Meego OS to replace Symbian
in Intel-based Nokia devices includes such a browser.

Apparently you have a different understanding as to what a Web app is.
Usually a Web app is understood as a markup document that is displayed in a
Web browser, even on mobile devices (like in Mobile Safari on iPhone or
Android-based devices). It appears as if you understand it to be an
application that embeds a browser component; that is, however, not a Web
app, but a native app (which can be installed through repositories like the
Apple AppStore or the Google Market). In the latter sense, you would be
correct; in the usual sense, you are not.
I could probably accomplish what I want more easily by dragging in a
framework like jQuery,
No.

but I want to avoid the bloat of increasing the size of my app,
and the user's data plan usage, by a factor of two.

And you want to avoid the unreliability that comes with it.
I think it may have something to do with loading scripts after the
window.onload event has already fired, so the scripts loaded after
that won't initialize properly if they rely on that onload event. I
can handle that situation in my own scripts, but not third-party
ones.

Talk is cheap. Show me the code.
-- Linus Torvalds


PointedEars
 
V

VK

If I load the scripts the traditional way, using <script> tags in the
document head, it works fine. But then I don't accomplish the effect
with the "busy while loading" graphic in the body.

Any suggestions or hints? I'm an experienced programmer, but
javascripts weird asynchronous behavior is bedeviling me.

The "ground zero" problem is that 15 years later Javascript is still
missing any normal library loading capabilities. So except the ol'good
<script src="library.js"></script>
<script>
// my stuff
</script>
except that programmers are left on cold with voodoo dancing around
runtime script implants: using document.write or DOM methods. A
descent Java-like native import, loadLibrary, loadClass etc. are
hugely overdue yet I don't see a movement in that corner.

Particularly in your case the matter is complicated by the fact that
Google Maps Javascript API file (http://maps.google.com/maps/api/js?
sensor=false) is not the actual library but a tricky loader of its
own. It uses document.write for script implanting and then queued
block loading over subsequent document.write calls. That means 2
things:
1) a loader calling loader that creates loaders will get dizzy or nuts
right away or at some point of time in the future
2) Google Maps Javascript API V3 loader is using the patented (in the
US) document.write trick so cannot be reliably used after the document
"load" event - unless writing your own API loader.

That makes the first part of the solution to be a prerequisite:

<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<meta http-equiv="Content-Type"
content="text/html; charset=utf-8">
<script
src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script>
// WHAT'S NEXT?
</script>
</head>
<body>
</body>
</html>

So "WHAT'S NEXT?" As I said before Javascript is still missing
conventional import tools so it all comes to different ways of
emulating such tools (a.k.a. voodoo dancing and singing). The one I
would start with: cyclic check on a particular member availability.
(Here and further I assume that the straightforward script blocks
hardcoded in your HTML document are not suitable for your project.)

So say you need to load lib/mapops.js which is dependent on Google
Maps Javascript API and then to execute the actual script that needs
both Google Maps and mapops.js. At the moment the actual script is
executed and its results are ready - at this moment the initial
"loading" content of the page must be replaced.

Three extra caveats on the go:

1) window.onload handler is nearly everywhere documented as "executed
when all page components are loaded" and similar. It is a complete
b.s.
window.onload is executed then DOM is ready (so you can address any
DOM part) but NOT RENDERED YET. That means that if onload you start
doing some time consuming script, your visitors may not see your
"loading" message for up to another 5-10 seconds, even if it's
hardcoded in the document body. Some newer UAs (Opera among them) do
act wiser in such situations but you should not count on it as of now.
So before doing anything onload, we need to release the context for
the initial rendering.

2) Some library may be simply unavailable (off-line mode, server is
down, misspelled name or path).

3) Animated GIF images may look like images but in fact these are mini-
programs written in a special primitive language and executed by
browser. If the engine gets really busy it sleeps on all lesser
important processes. The execution of GIF89a programs (GIF animation)
is reasonably considered as such lesser important process. That means
that browser may stop animation for a noticeable period of time. If
there is nothing but "loading" GIF on the page, your visitor may get a
wrong impression that the program got frozen.


We will handle (1) and (2) and skip on (3) for now.

<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<meta http-equiv="Content-Type"
content="text/html; charset=utf-8">
<script
src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script>

// Checking the state of things
// every 100ms
var attemptDelay = 100;

// Doing so many attempts before
// to give up
var attemptCount = 100;

window.onload = function() {
// Release context and init
window.setTimeout('init()', 0);
}

function init() {
if (typeof google.maps.Geocoder == 'function') {
// load your own library
// and do stuff
}
else {
if (attemptCount > 0) {
--attemptCount;
window.setTimeout('init()', attemptDelay);
}
else {
// ExternalLibraryLoadingError
// situation handling
}
}
}
</script>
</head>
<body>
<p>
<img
src="http://www.codedigest.com/img/loading.gif"
width="508" height="381">
</p>
</body>
</html>

Still for your case as described the solution is overly complicated
because I see you are using(?) "defer" flag for lib/mapops.js. "Defer"
doesn't mean "parse it some later". "Defer" means: "I do guarantee
that this script doesn't use document.write, DOM manipulations or
redirections so it is safe - though not necessary at all - to postpone
its execution till after the initial page rendering". Indeed a good
application has libraries where nothing "visual" is happening and the
program part where all happens. If your application is good - and I
believe so - then the whole thing comes nicely to:


<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<meta http-equiv="Content-Type"
content="text/html; charset=utf-8">
mapops.js
<script
src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script>

<script
src="lib/mapops.js"></script>
<script>

// Checking the state of things
// every 100ms
var attemptDelay = 100;

// Doing so many attempts before
// to give up
var attemptCount = 100;

window.onload = function() {
// Release context and init
window.setTimeout('init()', 0);
}

function init() {
if ((typeof google.maps.Geocoder == 'function') &&
(typeof geocoder == "object")) {
// generate the content, replace the "loading"
// provisory content and do the stuff
}
else {
if (attemptCount > 0) {
--attemptCount;
window.setTimeout('init()', attemptDelay);
}
else {
// ExternalLibraryLoadingError
// situation handling
}
}
}

</script>
</head>
<body>
<p>
<img
src="http://www.codedigest.com/img/loading.gif"
width="508" height="381">
</p>
</body>
</html>

- END -
 
M

Mike Duffy

:

Particularly in your case the matter is complicated by the fact
that Google Maps Javascript API file
(http://maps.google.com/maps/api/js? sensor=false) is not the
actual library but a tricky loader of its own. It uses
document.write for script implanting and then queued block loading
over subsequent document.write calls. That means 2 things:
1) a loader calling loader that creates loaders will get dizzy or
nuts right away or at some point of time in the future
2) Google Maps Javascript API V3 loader is using the patented (in
the US) document.write trick so cannot be reliably used after the
document "load" event

Is this the trick you described above, or a different trick?

Using document.write to inject scripts is something I did long ago.

Not only is the code hideously difficult to follow, but just escaping
quotes, (single & double) and backslashes in the embedded HTML
definitions can cause headaches.

I have since learned to do things differently.
 
D

David Mark

On Mar 17, 3:20 pm, (e-mail address removed) (axlq) wrote:

[...]
I could probably accomplish what I want more easily by dragging in a
framework like jQuery, but I want to avoid the bloat of increasing
the size of my app, and the user's data plan usage, by a factor of
two.

This is how it starts. What on earth makes you think that 70K of
incompatible CSS selector query engine code will help with your
strange desire to take over script loading from the browser. FWIW,
your proposed strategy won't make your app load faster (or even seem
to load faster). You don't need a loading indicator (or at least you
shouldn't). And the jQuery dev's have been bitten so many times by
their ridiculous "ready" hacks... What would make you want to
delegate such a responsibility to *them*? And for a mobile
application? Where virtually every device has queries built right
in?! :(

I mean, is it the bloggers? Are you stuck (as they are) in 2003?
 
V

VK

Is this the trick you described above, or a different trick?

The trick I described.
http://maps.google.com/maps/api/js?sensor=false
is an anonymous auto-executing function that uses document.write('<' +
'script src="' etc. to add the main library
http://maps.gstatic.com/intl/en_us/mapfiles/api-3/4/6a/main.js and
then a big set of subsequent document.write to add extra layers of the
library. You can easily observe it yourself by following the first
link.
Another outcome of this questionable approach is that Google Maps API
cannot be loaded in the default way for the "true XHTML", thus valid
XHTML served with application/xhtml+xml Content-Type, because it
doesn't support document.write method. The fact that nobody gives a
damn about it just shows the "popularity" of the true XHTML on the
Web :)
Using document.write to inject scripts is something I did long ago.
Not only is the code hideously difficult to follow, but just escaping
quotes, (single & double) and backslashes in the embedded HTML
definitions can cause headaches.

I have since learned to do things differently.

I would not put document.write and DOM altering into a "bad-good"
opposition. There are initial load situations when DOM is not
available yet and there are after load situations when document.write
is not usable. So each handles its own segment. The important things
is to remember that both are rather ugly workarounds because of the
lack of normal import tools. Even more important that ECMAScript group
would realize that as well.
 
Ad

Advertisements

A

axlq

That depends on what the app is. If it is a Web app, it does not make sense
as you approach it.

It makes sense for my web app. This isn't something that is intended to
be accessed from any old browser. It's an app that is served by a
server. It is intended to be run from a wrapper with no controls, such
as the internal browser provided by Meego.
That is not an ideal, it would be an accessibility nightmare. Robots could
not index anything, too.

I don't really care what robots can index, because standard browsers
(while the app works in them) are not the target of this app.
I am using an iPhone, and I have used an Android-based device for several
months. I can tell you that all Web apps I have used so far have not been
displayed differently than other Web sites with regard to the browser
chrome. That is good so because you want to be able to navigate away from
the Web app.

Those web apps aren't the same sort of animal. It's an app that is
accessed remotely, and the user isn't supposed to know he's using a
browser.
Apparently you have a different understanding as to what a Web app is.
Exactly.

Usually a Web app is understood as a markup document that is displayed in a
Web browser, even on mobile devices (like in Mobile Safari on iPhone or
Android-based devices). It appears as if you understand it to be an
application that embeds a browser component; that is, however, not a Web
app, but a native app (which can be installed through repositories like the
Apple AppStore or the Google Market). In the latter sense, you would be
correct; in the usual sense, you are not.

Right. You got it. We have an app written in PHP/HTML/Javascript
that is served up by a server. It should look like a stand-alone
app, not a web page in a browser. And iTunes doesn't accomodate
distribution of web app links; rather, you have to provide a
downloadable and installable wrapper that fetches the app.

An obvious reason for doing it this way is portability across multiple
platforms. Another reason is that updates are instantly deployed to all
users without requiring a re-install.

-A
 
A

axlq

[much helpful advice and examples]

Thanks! I was considering using setInterval() to check for the existence
of members in scripts that other scripts depend on before loading those
other scripts. When a check passes, the setInterval is cleared and the
script is loaded. What you suggested appears to be along those lines.

-A
 
Ad

Advertisements

T

Thomas 'PointedEars' Lahn

axlq said:
Thomas 'PointedEars' Lahn:

I don't really care what robots can index, because standard browsers
(while the app works in them) are not the target of this app.

That is shortsighted thinking.
Right. You got it. We have an app written in PHP/HTML/Javascript
that is served up by a server.

What else? And there is still no "Javascript".
It should look like a stand-alone app, not a web page in a browser.

And iTunes doesn't accomodate distribution of web app links; rather, you
have to provide a downloadable and installable wrapper that fetches the
app.

So, I understand you are primarily distributing a native app as I described
above?


PointedEars
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top