Blog

Error handling in Ajax applications

With the introduction of Ajax in your web-application you move part of the processing from the server to the client. This creates the opportunity to build an application that is both highly responsive and has a rich user interface. As a bonus you also reduce the burden on the server. Since there is usually only one server and there are many clients this is a good thing. However, by moving your code you als loose control. In this article I will explain why to be in control of your code is important and how to get it back again in your Ajax application.

Note: non technical readers can read this article and just skip all technical (code) details.

First, let us find out what it means to be in control. Let us consider a really old school approach: a Java application server (e.g. Tomcat) which processes requests in Java servlets what results in static html pages. No JSP even, so the whole thing is compiler safe.

Now, when this application is in production every single error will be logged. You'll find the type of error, the call stack and the line numbers in the source file. Everything you need rom a technical viewpoint. What more could you want? Well, may be some process and business information. Log the username, email and telephone number and you can contact the person and ask whether he or she would appreciate some help.

Although there is an independent process running, even while you are away and asleep, you are in control. You can monitor everything what has happened and what * not* has happend or should not have happened.

Example of an error log:


Apr 14, 2008 4:45:10 PM org.apache.catalina.core.StandardWrapperValve invoke

SEVERE: Servlet.service() for servlet teachern1Servlet threw exception

java.lang.NullPointerException

at user.usersession.getname(usersession.java:52)

at teacher.teachern1Servlet.processRequest(teachern1Servlet.java:169)

at teacher.teachern1Servlet.doGet(teachern1Servlet.java:234)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:690)


Clearly is stated that in package 'user' object 'usersession' a problem occurs in the method 'getname()' because a variable has not been initialized (a so called NullPointerException). You can also track the context from which this object has been called.

The immense advantage of this approach is twofold:

  • you can proactively give service to the user/ customer, and
  • you can quickly improve your application by fixing bugs in just a matter of days.

This becomes even more important in an environment where users are only loosely coupled to your organization and have a choice whether to use your services or not. A failing web application that doesn't get fixed over time hurts the image of your organization. A lack of control and necessary response on the interface offered to the end user is nothing less than organizational arrogance. 'We do not care' - that is the message you'll be sending implicitly.

On top of these arguments there is the challenge of rising complexity of today's web applications and diversity in client software that we'll have to deal with.

  • Logging is part of the design of a system, it is important and not an afterthought for programmers
  • Logging serves technical and process/ business purposes.

Code running on clients is out of reach for us, unless we design for feedback. For desktop applications this is quite common. When an application crashes, we get a window that asks us permission to send a detailed crash report to the server.

The next piece of this article is about implementing such a solution for Javascript web applications.

A simple implementation for Javascript client code

What we need:

  • a way to trap errors when they occur
  • a mechanism to send feedback to a server
  • a process running on a server to receive the feedback message

Server side: java servlet that receives the feedback message

A simple (but good enough) example of a Java servlet that will take the requests sent to jstalkback.jsv. The request will contain POST (or GET) data with details of the error. Eventually it may send a response back to the client for which a dummy response writer has been added.

public class jstalkbackServlet extends HttpServlet {

   Logger.getLogger("nl.delarte.jstalkbackServlet");

   protected void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

      String msg = "";

      // read message
      if (request.getParameter("msg") != null) {

         msg = request.getParameter("msg");
      }

      // read url where error occurred
      String url = "";

      if (request.getParameter("url") != null) {

         url = request.getParameter("url");
      }

      String line = "";

      // read the line number
      if (request.getParameter("line") != null) {

         line = request.getParameter("line");
      }

      // write the error in the log file
      logger.severe("javascript error: " + msg + " page: " + url + ": " + line);
      response.setContentType("text/xml");

      PrintWriter out = response.getWriter();

      // insert any message here
      
      out.close();

    }

In this example the messages are sent to a plain log file. Let your system guru watch that file every other day. Or you might be tempted to feed them directly in your incident managing system.

If you have a logged on user you'll probably use a session object for this. Access this session object to retrieve the user information to add to the log.

   // you'll have to create a class userSession

        userSession us = null;

        if (session.getAttribute("user") != null) {

            us = (userSession) session.getAttribute("user");

        } else {

            response.sendRedirect("logon.jsv");

            return;

        } 

    // you'll need to make a method getName()
    ... us.getName();

Client side: javascript

I suggest you'll put the code that handles the error in a javascript file separate from the application/ page specific code. In my case this file has the not so original name utils.js.

So every js-file includes at the top of it the next two lines:

// requires utils.js
onerror = handleErrorTalkBack;



Then there is the code in utils.js:
function handleErrorTalkBack(cmsg,curl,nline) {
   var cp = makeurlparam("msg", cmsg);
   cp += makeurlparam("url", curl);
   cp += makeurlparam("line", nline);

   var request = new ajaxObject("jstalkback.jsv", processjstalkback);
   request.update('or=' + norgid  + cp, 'POST', true);
   return true;
}

function processjstalkback() {
    alert("An error has occurred.\nCheck whether the changes have been applied.\n");
}
function makeurlparam(cname, cvalue) {
    return("&" + cname + "=" + encodeURIComponent(cvalue));
}
// universal standard ajax object
// http://www.hunlock.com/blogs/The_Ultimate_Ajax_Object

function ajaxObject(url, callbackFunction) {
   var that=this;
   this.updating = false;
   this.abort = function() {
      if (that.updating) {
         that.updating=false;
         that.AJAX.abort();
         that.AJAX=null;
      }
    }


  this.update = function(passData,postMethod, luserandomparam) {
        if (that.updating) {
            return false;
        }
        that.AJAX = null;
        if (window.XMLHttpRequest) {
            that.AJAX=new XMLHttpRequest();
        } else {
            that.AJAX=new ActiveXObject("Microsoft.XMLHTTP");
        }
        if (that.AJAX==null) {
            return false;
        } else {
            that.AJAX.onreadystatechange = function() {
                if (that.AJAX.readyState==4) {
                    that.updating=false;
                    that.callback(that.AJAX.responseText,that.AJAX.status,that.AJAX.responseXML);
                    that.AJAX=null;
                }
            }
            that.updating = new Date();
            if (/post/i.test(postMethod)) {
                var uri=urlCall;
                if (luserandomparam) {
                    uri += '?'+that.updating.getTime();
                }
                that.AJAX.open("POST", uri, true);
                //       that.AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                that.AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
                //         that.AJAX.setRequestHeader("Content-type", "text/html;charset=UTF-8");
                that.AJAX.setRequestHeader("Content-Length", passData.length);
                that.AJAX.send(passData);
            } else {
                var uri=urlCall+'?'+passData+'×tamp='+(that.updating.getTime());
                that.AJAX.open("GET", uri, true);
                that.AJAX.send(null);
            }
            return true;
        }
    }
    var urlCall = url;
    this.callback = callbackFunction || function () { };
}

For the call to the server I used the the Ultimate Ajax object from Patrick Hunlock. (Update 2013: using JQuery is more common nowadays.) The variable names are decorated with so called Hungarian notation. This helps to avoid confusion on the variable type.  

When implemented the result in the log file will look like this - provided you have some error in your javascript. You might even need to make one :-).

Mar 26, 2009 1:22:41 PM utils.jstalkbackServlet processRequest
SEVERE: javascript error: orderx3_dopageload is not defined page: http://www.delarte.nl/aom/orderx3.jsv?or=2&od=1255&n=1: 295
Overname alleen na voorafgaande toestemming.