Saturday, June 23, 2012

HTML <script> tags 101: a JavaScript refresher

Time to revisit the basics with a quiz.

What is the output of this code when run on an otherwise valid HTML page?

<script>
  console.log("DEBUG 1");
  var foo = ;  // Javascript syntax error
  console.log("DEBUG 2");
</script>
<script>
  console.log("FOO");
  console.log("BAR");
</script>

You will get a syntax error along the lines of  Uncaught SyntaxError: Unexpected token ; Beyond that what will be printed to the Javascript console?

Answer Choices

  1. All console log lines print out:

    DEBUG 1
    DEBUG 2
    FOO
    BAR

  2. Console line with DEBUG 2 fails because of the syntax error before it:

    DEBUG 1
    FOO
    BAR

  3. The first script block fails because of the syntax error and the second block is fine:

    FOO
    BAR

  4. The line above the syntax error prints, but all subsequent Javascript is hosed:

    DEBUG 1

  5. No output, since the syntax error stops all Javascript in its tracks.

Before you read any farther, follow Kent Beck's advice: stop and answer out loud.

In many large and aging JavaScript and HTML code bases, there are little syntax errors that get ignored in the heat of getting a release out the door and moving on to higher priority features and bug fixes. It passes QA so the syntax error noise gets ignored.

But sometimes those seemingly harmless little syntax errors can cause big problems. More on that in a minute.

The answer to the quiz above is choice #3.

JavaScript is not a line-by-line parsed scripting language, like a bash script, but it is a script-by-script parsed language.

If I run a bash script like this:

#!/bin/bash
echo "foo"
eho "bar"  # syntax error
echo "quux"

it will print out:

foo
./myscript.bash: line 3: eho: command not found
quux

A bash script just parses and executes each line independently of the others. Syntax errors only affect the current line.

HTML <script> tags are fully parsed, possibly compiled (depending on the context/browser) and only run if the previous steps worked. But the next <script> tag is independent of the previous in terms of whether it will run.


/* ---[ Lessons Learned ]--- */

So why is this important?

Suppose you are doing Javascript module requires. With RequireJS, you do a require like this:

<script>
  require(['customerModule', 'orderModule'], function(c, o) {

    var customers = c.getCustomers();
    // do something with customers ...

    var orders = o.getOrders();
    // do something with orders ...
  });
</script>

or with earlier versions of Dojo, you might have a series of requires of its dijit widget library:

<script>
  dojo.require("dijit.form.Button");
  dojo.require("dojox.layout.ContentPane");
</script>

What if you have a simple syntax error in the block of script code? All the requires will fail, but the page will chug along and try to execute the rest of the javascript in other script tags or files.

If you have code like this:

<script>
  require(['customerModule', 'orderModule'], function(c, o) {

    var customers = c.getCustomers();
    // do something with customers ...

    var orders = o.getOrders();
    // do something with orders ...
  });

  var foo = ;  // our syntax error again
</script>

Your app will probably fail now. So easy, just fix the obvious syntax error and assign something to foo. The tricky part is maybe that that assignment value is set server-side in ASP, JSP, or PHP code and some code path ended up having a null value, ending up in that JavaScript syntax error.

So you need defensive programming here on both sides - on the server side protect variable interpolation from delivering null or empty entries when dynamically creating Javascript code.

That could be something like this JSP/JSTL code:

<c:if test="${myval}">var foo = ${myval};</c:if>

On the Javascript side, separate your set up code (such as module requires) from "regular" executable other code as much as possible.

No comments:

Post a Comment