What is a non-blocking script?

It was just a couple of years ago that Steve Souders introduced the concept of blocking vs. non-blocking into the common lexicon of web developers around the world. His big focus was pointing out how <script> tags block the rendering of the page as well as the downloading of other resources. Obviously, this is really bad for your initial page load, where a single high-latency <script> tag causes your page to appear blank for a large amount of time. Even with all of this discussion, it seems that there’s still some confusion over non-blocking scripts and how they relate to parallel downloads. This is my attempt to clear up the confusion.

JavaScript is single-threaded

To begin, you need to understand a little bit about JavaScript and the browser. JavaScript is fundamentally single-threaded, meaning that only one operation can be performed at a time. Further, this single thread is actually shared between JavaScript execution and browser rendering. This is typically referred to as the UI thread of the browser and is usually the focus of rendering-related performance discussions.

The browser can only be executing JavaScript or rendering UI at any particular point in time (it can’t be doing both). This makes sense logically, because JavaScript may affect the UI by moving elements around or otherwise altering content, and the next time the UI is updated the browser wants to be sure the latest information is used.

With this knowledge, think of what happens as a page downloads to the browser. The page has started to render as it was downloaded, and then a <script> tag is encountered. At that point, the browser can no longer continue rendering because the JavaScript may affect the UI, and so it waits. The HTTP connection is made, the file is downloaded, parsed, and executed. Only once that is complete can the browser continue to render the rest of the page in full confidence that the output is up-to-date.

Parallel downloading

Older browsers would actually stop doing everything, including downloading additional resources in the page, while a script was downloading. That meant two <script> tags in a row would result in the browser waiting to begin download of the second script until after the first was downloaded and executed. Newer browsers will download the script files in parallel and then execute them in order, so the second script is ready to be executed as soon as the first complete (for more information, read Steve’s post on this).

Parallel downloading should not be confused for asynchronous execution. Remember, JavaScript is single threaded, so you literally cannot be executing two scripts at the same time. Parallel downloading of scripts only means that two scripts are downloaded at the same time, not that they’re executed at the same time. There’s a big difference.

By downloading more than one JavaScript file at a time, you’re saving time on the downloading of resources only. This can turn out to be significant if you’re dealing with a high-latency connection versus downloading the script files sequentially. Just keep in mind that the scripts are still executed in order and only one can be executed at a time.

Non-blocking scripts

Steve also wrote a post about how to load JavaScript in a non-blocking manner (actually, he gives multiple ways of doing this). The basic approach that I’ve recommended is to create a script node dynamically. As opposed to using a

A blocking script means that the page cannot continue rendering until the script has been:

  1. Completely downloaded
  2. Parsed
  3. Executed

In many cases, it’s #1 that takes the longest. Parsing and execution of JavaScript is pretty fast, especially in the newer browsers with optimizing JavaScript engines. Network latency and the overhead of an HTTP connection is usually the slowest part of the process. When a script is loaded in a blocking manner, that response time for the script is roughly equivalent to the amount of time that the browser is not rendering. That’s a lousy user experience, and that’s exactly what you get when an external script is loaded using the <script> tag.

Using a dynamic script node causes external JavaScript files to be downloaded in a non-blocking way. This means the browser doesn’t need to wait for the file to download before continuing to render. In effect, #1 (and probably #2) in the previous list no longer cause the UI thread to stop. But since there is only one thread, the actual execution of JavaScript once the file is downloaded will still block rendering. However, as mentioned before, execution is often the fastest part of the sequence and so this goes largely unnoticed by users (assuming you’re not doing something insane in that script).

So loading scripts in a non-blocking way basically frees up the browser to continue rendering while the script file is being downloaded. The *loading *of these files is done asynchronously, but *execution *will still cause the UI thread to block for a small amount of time.

The HTML5 async attribute

HTML5 introduces a new attribute on the <script> tag called async. This is a Boolean attribute (doesn’t require a value) and, when specified, causes the script file to be loaded as if you had created a dynamic script node. Basic usage is as follows:

<script type="text/javascript" async src="foo.js"></script>

When supporting browsers see the async attribute (only Firefox 3.6 currently supports it), it knows that the script file can be downloaded without blocking rendering. This is really a convenient way to load files in a non-blocking manner versus using a JavaScript function to do the loading.

The async attribute is still a bit misunderstood, and has some side effects based on browser behavior. When set using HTML, the behavior is very straightforward as discussed earlier. When set on a dynamic script node, the behavior has a subtle distinction. Firefox and Opera preserve the order of execution for external JavaScript files, so you are guaranteed that scripts will execute in order when two dynamic script nodes are added one after the other. So in Firefox 3.6, setting async on the first script informs the browser that it need not wait to execute this script before executing others that may come after it. When Opera implements this feature, it will likely work the same way. This is the apparent motivation behind the Google Analytics source code that creates a dynamic script node and then sets async on it. Internet Explorer, Safari, and Chrome do not preserve the order of execution, as scripts are executed as soon as they are retrieved regardless of the order in which they were inserted. In these browsers, setting async on script nodes has no effect (but also doesn’t hurt anything).

The async attribute is still a bit misunderstood, as evidenced by the Google Analytics source code that creates a dynamic script node and then sets async on it. Doing so is redundant since dynamic script nodes are loaded asynchronously already. The async attribute is only really useful when <script> is included directly in HTML.

Conclusion

There are basically two ways to achieve non-blocking (aka asynchronous) JavaScript downloading: create a script node dynamically and use the HTML5 async attribute of a <script> tag. Combining this with the capability of parallel script downloads in newer browsers means that your page can take less time to render fully to the user. Try to avoid blocking JavaScript downloads whenever possible.

Update (10 August 2010): Fixed small typos and updated description of async attribute to reflect Steve’s and James’ comments.

Comments

  1. Ramon Lechuga

    Nice, thanks Mr Zakas, now we don't need to load scripts using Ajax, the browser will do it by it self, can't wait to start using this feature of HTML5, I might thing that this feature will come with the onload event isn't it?

  2. Steve Souders

    Great article.

    ONE VERY IMPORTANT WARNING: You recommend dynamic script tags as the best approach to use for async script loading. However, this technique does NOT preserve execution order in IE, Chrome, and Safari.

    Here's a test page: http://stevesouders.com/cuz... . The first script is from "1.cuzillion.com", the second one is from "2.cuzillion.com". At the bottom of the page it shows the order of when each script is executed. If there's a code dependency, 1.cuzillion.com's script needs to be executed before the 2.cuzillion.com script - otherwise the user gets undefined symbol errors. In Firefox and Opera, 1.cuzillion.com is executed before 2.cuzillion.com, which is correct. In IE, Chrome, and Safari, however, 2.cuzillion.com is executed first.

    If you have multiple scripts that depend on each other and you can't combine them, please check out the other techniques in my "loading scripts without blocking" blog post ( http://www.stevesouders.com... ).

    It's exactly this execution order issue that is the likely motivation for Google Analytics setting the async attribute. Suppose a page has ga.js and main.js, both of which are inserted using dynamic script elements. Main.js doesn't depend on ga.js, so it's unfortunate that main.js will be blocked from executing waiting for ga.js. Setting async=true tells the browser it doesn't have to block subsequent scripts, which is a good thing.

  3. James Burke

    Related to the comment on Steve Souders: I set the async attribute for dynamically created tags in RequireJS to allow the script to be executed as soon as possible in Gecko -- otherwise, script tags that are essentially long polls for comet-like connections (like real-time chat) could block the execution of the script.

    Dynamically created script elements that are used for long-polling should be encouraged to set the async attribute, to help avoid blocking the execution of other dynamically created scripts tags in Gecko. Hopefully Opera will support the async attribute and get a similar benefit eventually. Otherwise, supporting long polling with dynamic script loading in Firefox would require tricks like an iframe to house the long-polling connections.

    It would be great to have some more official tests for this long-polling execution block workaround via the async attribute, to compare the behavior of browsers over time. Hopefully it will show that Gecko benefits now, with Opera benefitting later as they implement the attribute support.

  4. Aaron Peters

    Very informative article Nicholas, txs.
    And I guess that closes our conversation via email ;-)

  5. Vincent Voyer

    Nice article.

    Two of the "best" techniques for me when optimizing client's websites and get parallel downloading works on every browser :
    - using defer as mentionned in Steve Souders blog post (IE6-8), the only drawback is that inline scripts can't have dependencies from the external scripts. (execution order of external scripts is ok, see http://hacks.mozilla.org/20....
    - using http://labjs.com/ wich use the dynamic dom node insertion but handle the hassle of cross browser problem you can have. Also if scripts have dependencies you can handle it well (both external and inline)

  6. Nicholas C. Zakas

    @Aaron - Yes, this started out as an email to you. But then I figured I'd just share with everyone. :)

    @Sidnei - I purposely left out defer. I think that would confuse the main topic.

  7. lenel

    defer equals domready .it's excuting is soslow special in 3rd party content developer

  8. lenel

    a example show why async is necessory for dom script element in firefox 3.6
    before loaded it block the next script element from excute like this
    http://lifesinger.googlecod...
    an aysnc attribute help to fix it .

  9. Kyle Simpson

    Great article and very clear explanation, Nicholas. Thanks for posting it.

    I wanted to briefly mention a particular "argument" that many make against the need for non-blocking loading: "just concat all files into one and then there's no need to dynamically load it." It's amazing how common a mis-conception this is. I believe it's mostly due to the YSlow rule #1 about reducing HTTP requests (and thus overhead/latency delays).

    Obviously, I agree that reducing your page's requests is a great idea. If it makes requests for 10 or more scripts, you're probably doing it wrong. But reducing to only 1 script has a number of possible disadvantages, which I've talked about in detail here: "Why not just concat?"

    One of my main arguments for not concating to only one large file is that you lose the ability for the browser to download in parallel, as you've mentioned here. The assumption (and I believe misconception) is that HTTP connection overhead is so much more overwhelmingly bad that it negates any possible benefit that most people would see from parallel downloading,for instance, two 50k files instead of one 100k file.

    I am now conducting a performance-benchmarking test to examine this exact issue (concat'ing to one file or splitting into 2 or 3). I'd really love it if as many people as possible could go and click each button several times and then pass the link along.

    Concat performance benchmarking test #1

    Hopefully we can start to get some roughly constructed numbers around how single files vs. parallel files actually behave in terms of time-to-download. I think then we'll have a clearer argument for why non-blocking loads (and script loaders that can handle them and still sequence execution) are really important.

  10. Drew Marsh

    How exactly would giving a brief history lesson on IE's defer "confuse the main topic"? It's awesome that the other people are finally coming around and realizing there's value in such a feature now (hence async in HTML5), but would it kill us to give a little credit to the IE team for understanding the value of such a thing **13 years** ago?

  11. Erik Arvidsson

    The async attribute is supported by WebKit

    https://bugs.webkit.org/sho...

    Based on the date of the patch it should be available in Chrome dev and beta.

  12. James Burke

    Related to my previous comment, I put up a test page for the async attribute in dynamically created script elements, to demonstrate its usefulness in scenarios when long polling is involved with Gecko/Firefox browsers:

    http://www.tagneto.org/brow...

Understanding JavaScript Promises E-book Cover

Demystify JavaScript promises with the e-book that explains not just concepts, but also real-world uses of promises.

Download the Free E-book!

The community edition of Understanding JavaScript Promises is a free download that arrives in minutes.