most browsers, errors can be caught
by a top-level window.onerror()
handler that provides a browser-spe-cific message, file, and line number.
Simply dumping these messages to
a user-visible console represents a major step forward, but even an accurate
message, file, and line number can be
worthless when debugging a problem
in an AJAX application. Unless the bug
is a simple typographical error, we
need to better understand the context
in which the error was encountered.
Faced with an unexpected error,
the next question is almost always:
“How and why are we here?” If we’re
lucky, we can just look at the source
code and make some educated guesses. The most common method of improving this process is through stack
traces. The ability to generate stack
traces is the hallmark of a robust programming environment, but unfortunately this is also one feature often
overlooked. Stack traces are often
viewed as too difficult to construct,
too expensive to make available in
production, or simply not worth the
effort to implement. Because they are
commonly viewed as something that’s
required only in exceptional circumstances, stack traces can often be expensive to calculate. As the complexity
of a system grows and as asynchrony is
employed to a larger extent, however,
this view becomes less tenable. In a
message-passing system, for example,
the context in which the original message was enqueued is often more important than the context of the failure
once the message has been dequeued.
In an AJAX environment (where
asynchronous was worthy of a spot in the
acronym), the need for closures often
makes the context in which they have
been instantiated more useful than
the closures themselves.
Sadly, JavaScript support for stack
traces is sorely lacking. The browsers that do support stack traces make
them available only via thrown exceptions, and most browsers don’t provide them at all. Stack traces are never
available within global handlers such
as window.onerror(), as the arguments are defined by a DOM that optimizes for the lowest common denominator. A window.onexception()
handler that’s passed as an exception
object would be a welcome addition.
When a bug is
encountered in
production, enough
information must
be preserved such
that the root cause
can be accurately
determined, and
this information
must be made
available in
a form that can be
easily transported
from the user
to engineering.
Instead, we’re forced to catch all exceptions explicitly. On the surface,
this seems like a daunting task—we
don’t want to wrap every piece of code
in a try/catch block. In an AJAX application, however, all JavaScript code is
executed in one of four contexts:
˲Global context while loading
scripts;
˲ From an event handler in response
to user interaction;
˲ From a timeout or interval; or
˲ From a callback when processing
an XMLHTTPRequest.
The first case we must defer to
window.onerror(), but since it
happens while scripts are loading,
it would be hard for such bugs to escape development. For the remaining cases, we can automatically wrap
callbacks in try/catch blocks through
our own registration function as illustrated in Figure 1a.
The table here describes the information that is available from a global
context and when catching particular
types of exceptions for different browsers. The table demonstrates the limits
of integrated browser support. Without
reliable stack traces on every exception,
we are forced to generate programmatic
stack traces for better coverage. Thankfully, the semantics of the arguments
object allows us to write a function to
generate a programmatic stack trace as
depicted in Figure 2.
A full implementation would provide a means for skipping uninteresting frames, including native stack
traces (via a try/catch block), and providing a toString() method for
converting the results. We don’t have
file and line numbers, but we do have
function names and arguments. Sadly,
the proliferation of anonymous functions in JavaScript makes it difficult
to get the canonical name of a function. The toString() method can
give us the source for a particular function, but when printing a stack trace
we need a name. The only effective
way to accomplish this is to search the
global namespace of all objects while
constructing a human-readable name
for the function along the way. This
seems expensive, but we need to print
the stack trace only in case of error.
Most functions are either in the global
namespace, one level deep, or two levels deep in the prototype of a particu-