Ajax's Odyssey

Like the Greek hero Ajax, there are many triumphs and failures that our AJAX requests incur at Yext. Because a single AJAX call contains many diverse parts, it can be difficult to debug. While I’m no Homer, I’ll do my best to document the journey of a mythological AJAX request who has seen better days. You will doubtlessly encounter your own AJAX tragedies, and I hope that the tale I weave here will help guide your own AJAX calls to victory.

Surveying the landscape

Our adventure begins, as it so often does, with a busy sage / product manager. They point us to a URL and ask us to fix a button that appears to do nothing when clicked. Luckily, we know the general steps involved in an AJAX request, which gives us an idea of paths to we could start down.

  1. JavaScript listens for the button’s click event
  2. The event handler makes an AJAX call when the button is clicked
  3. The AJAX call hits an endpoint with a request and some data
  4. The endpoint URI routes to a backend Java method
  5. The Java method executes and renders a response back to the page
  6. The AJAX call completes a callback on the response’s success or failure

For pedagogy’s sake, we’ll begin with step one.

Hunting for JavaScript

Calling upon the powers of Chrome Developer Tools, we open the Elements tab and find our button in the DOM. Ultimately, we’re looking for the JS file containing the event handler, so we dig for some clues that may help us get there.

First, we check out the Event Listeners subtab. Under the click grouping, we see some event handlers and their file origins, but the JavaScript has been minified and concatenated into a single file. Ordinarily, we could click on the file name and view the source code right in our Developer Tools, but we’ll have to find another way to our file.

Event Listener tab in Chrome Developer Tools

Fortunately, Yext has adopted the habit of prefixing all classes selected by JavaScript with js-, so the button’s class js-defeat-trojans is a valuable lead.

Element tab in Chrome Developer Tools

Using Sublime Text, we do a multi-file search (⌘⇧F on Mac) for the string js-defeat-trojans. This will show us every place it’s used in our project, which will give us important context and ensure a fix in one place won’t cause a break in another.

Multi-file search query in Sublime Text

It appears that the selector is used only once in our JavaScript, so we click our search results to open up the JavaScript file immediately.

Multi-file search results in Sublime Text

Upon initial inspection, everything looks normal in the button’s event handler.

yext.TrojanWar.bindEventHandlers = function() {
  // add event handler for button click
  $('.js-defeat-trojans').on('click', fuction() {
    var $greaterajax = $('.js-ajax-great');

    // search for HTML elements; send results with request
    var requestData = {
      hasSword: $greaterajax.find('.js-weapon').length > 0,
      hasShield: $greaterajax.find('.js-shield').length > 0,
      numTrojans: $('.js-trojan-warrior').length

    // send ajax request
      dataType: 'json',
      type: 'POST',
      url: '/askoracle/defeatTrojans',
      data: requestData,
      success: function(response) {
        if (response.responseText) {
      error: function(response) {
        if (response.errorText) {

Since nothing looks logically incorrect, we’ll assume that there is some mismatch between the front and the back ends and begin our search into Java.

Down a twisting route

The jQuery AJAX request points to a URL that we know routes to a Java method. Yext uses the Play! framework, and in Play! 1.x, route files follow the pattern <project>/conf/routes. Using Sublime’s Goto Anything feature (⌘P on Macs), we filter for trojan routes, since searching for just routes would pull up the route file for every project.

Sublime Goto Anything

In that file, we see that our route points to the Java method Trojan.defeatTrojans.

Play! routes file

The land of Java

To uncover the backend, we open the Trojan class and locate its defeatTrojans method.

public static void defeatTrojans(
  Boolean hasSpear,
  Boolean hasShield,
  Integer numTrojans) {

  JSONObject response = new JSONObject();
  String prophecy = "According the the prophecy, ";
  response.put("isDefeated", false);

  // send error if any params are missing
  if (hasSpear == null || hasShield == null || numTrojans == null) {
    prophecy = "Your gifts do not inspire a clear response."

    response.put("errorText", prophecy); // add as error text
    renderJSON(response); // render and exit early

  // send prophecy based on request data
  if (hasSpear && hasShield) {
    if (numTrojans < 100) {
      response.put("isDefeated", true);
      prophecy += "the mighty Trojans will fall.";
    } else {
      prophecy += "the battle will be lost but a new day brings hope.";
  } else {
    prophecy += "u ded, gg";

  response.put("responseText", prophecy); // add as normal response text

The input parameters and responses all look fine here as well. At this point, we’re feeling confused enough to start printing some log statements. There doesn’t seem to be an obvious place to add one in the Java, but perhaps our JavaScript callbacks will be more fruitful.

The long journey home

Back in our JavaScript, we add a console.log statement that prints the whole request object.

  // ...
  success: function(response) {
    console.log('Success', response);
    // if (response.responseText) {
    //   alert(response.responseText);
    // }
  error: function(response) {
    console.log('Error', response);
    // if (response.errorText) {
    //   alert(response.errorText);
    // }
  // ...

To our surprise, the console always prints success and consistently has errorText instead of responseText. In our Java, the only time errorText is set is when a parameter is null. Logging each parameter reveals that hasSpear is always undefined.

We double check our request data and notice that the request data passes hasSword instead of hasSpear. A dip into the file’s history reveals that the property in Java was recently renamed from sword to spear, but the change was neglected in the JavaScript.

The Network tab in Dev Tools reveals that the ‘error’ always returns with a response of 200, so the error callback is never triggered. By using the static variable response that is inherited by Play! controllers, we can manually change the status to 400 for a bad request.

if (hasSpear == null || hasShield == null || numTrojans == null) {
  prophecy = "Your gifts do not inspire a clear response."

  response.status = 400;
  responseJSON.put("errorText", prophecy); // add as error text
  renderJSON(responseJSON); // render and exit early

Stories retold

Like any good Greek myth, the specific details and sequence of events will change with the particular storyteller. Similarly, your experiences debugging an AJAX request will vary based on your work flow and the bug you’re attempting to fix. Perhaps you are well versed with this story already, but I hope that a few tricks from this tale will find their way into your retellings.

Nicholas Cassleman

Nicholas joined Yext in Feburary 2015 after a batch at the Recurse Center where he dug deeper into JavaScript than recommended by the FDA. Before that, he studied game design at the University of Chicago. He lives in Brooklyn, is a member of the Brooklyn Botanic Garden, and is frequently spotted doing handstands.

Read More