I'm using the Drupal AJAX framework to load bits of content via AJAX. Sometimes, when a link is clicked and the usual blue AJAX loader shows, the AJAX does not complete (for whatever reason). Does anyone know how I can hook into that process and display a message if it takes longer than x amount of time, and possibly cancel the whole AJAX process too.

I'd be grateful for any ideas.

Comments

You are better off debugging

You are better off debugging the problem that is causing the problem. Sometimes it is a badly written script that uses a lot of resources when executing, sometimes it's an error in the javascript and it can even be javascript from a completely different module, and sometimes it's an error on the server that sends back bad code, which confuses the javascript.

The best way to debug this is to run your page with a javascript console enabled. Most browsers have them nowadays, though with firefox you can use the firebug add-on. Enable the console, load your page and run your ajax, and see if you get any information.

Jaypan We build websites

Sorry, my question wasn't

Sorry, my question wasn't very clear at all. I don't actually have a problem with the script, it works fine. I'm talking about when a general problem occurs, like an internet connection problem, or a client browser problem, something not caused by my code.

I saw something similar in a site once (possibly YouTube), I clicked an AJAX link, and my internet connection was rubbish at the time so the content never loaded, but then a friendly message came up advising me the request was taking longer than expected and I should refresh the page, or something to that effect.

I guess I'm wondering if I can add a timer somewhere, then hook into ajax.success or ajax.complete to cancel the timer. So if the timer then reaches a certain time, which would mean the request is taking too long, a message could then appear.

Update:

I'm wondering if this is the correct way to override the ajax.beforeSend and ajax.complete functions:

..
// Bind the ajax event to link with custom settings, etc...
Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);

var ajax = Drupal.ajax[base];

// Override beforeSend to include start timer
ajax.options.beforeSend = function (xmlhttprequest, options) {
  console.log('override beforeSend'); // test this is working
  // start timer code here
  ajax.ajaxing = true;
  return ajax.beforeSend(xmlhttprequest, options);
}

// Override complete to end timer
ajax.options.complete = function (response, status) {
  console.log('override complete'); // test this is working
  // end timer code here
  ajax.ajaxing = false;
  if (status == 'error' || status == 'parsererror') {
    return ajax.error(response, ajax.url);
  }
}
..

It seems to work. I should be able to start my timer in ajax.beforeSend and end it in ajax.complete now. I can't think of any possible problems that may arise doing it this way.

A few things. First,

A few things. First, ajaxComplete will never fire, since that is your problem - the AJAX request isn't completing! Second, as much as possible, it's best not to run timers as a background process. They can get heavy on some systems, particularly if they have memory leaks.

In this case, you can use $.ajaxSetup(), and set a timeout. For example:

$.ajaxSetup({
  timeout:10000
});

The above will timeout the ajax request in 10 seconds. Note that this is for all ajax requests on your entire system. I believe you will need to set up $.ajaxError() to handle the error that will result from timing out. It's in this function that you should be able to create the popup you want.

Jaypan We build websites

The reason I want to override

Interesting points. The reason I want to override is because I don't want this functionality in all AJAX requests, just my custom ones. So it seem logical to me to do it this way?

Also, if ajaxComplete doesn't fire then it won't end the timer, resulting in it counting down and then displaying the error message. So I'm actually counting on it (excuse the pun) not to fire to display my message, if it does fire then it'll just remove the timer and carry on as usual.

I don't know too much about timers and memory usage used, but I know sites do use timers. Also, there will only ever be one timer in use. Wouldn't timeout in $.ajaxSetup use a timer anyway?

At the moment, I have something like this:

var Ajaxify = Ajaxify || {};

Ajaxify.timerStart = function() {
  Ajaxify.timer = setTimeout(Ajaxify.timerTooLong, 10000);
}

Ajaxify.timerEnd = function() {
  clearTimeout(Ajaxify.timer);
}

Ajaxify.timerTooLong = function() {
  // popup message
  console.log('popup message');
}

and I put those function calls in the overridden ajax functions:

  var ajax = Drupal.ajax[base];
  // Override beforeSend to include timer start
  ajax.options.beforeSend = function (xmlhttprequest, options) {
    Ajaxify.timerStart();
    ajax.ajaxing = true;
    return ajax.beforeSend(xmlhttprequest, options);
  }
 
  // Override complete to include timer end
  ajax.options.complete = function (response, status) {
    Ajaxify.timerEnd();
    ajax.ajaxing = false;
    if (status == 'error' || status == 'parsererror') {
      return ajax.error(response, ajax.url);
    }
  }

and it appears to work how I want, though I need to go and test it properly. You think this is a bad way to do it? You got me thinking about using a timer though (and memory usage), I'm going to have a look more into using one, maybe do some testing on jsPerf.com.

update: I've just read that using the timer shouldn't be a problem, regarding memory leaks, because the timer should kill itself when it's finished counting down (after 10 seconds). So if I ensure there is only ever one timer running, it should be fine?

update: I've done some testing and it seems to work perfectly. I'm mimicking a bad connection (or problem), using the sleep(10); in my page callback.

I'm sure if you dug around,

I'm sure if you dug around, you could target the specific requests you want to target with the timeout inside ajaxPrepare(). Using timers is a bad practice. Just because lots of people do it, doesn't mean they are doing it right - there is way more bad javascript out there than good (I'm a bit of a JS fanatic).

If you code it right, there will be no memory leaks, but are you sure you know your JS well enough to code it right? No one intentionally puts memory leaks in their code, yet lots of scripts out there have them.

Anyways, if what you have is working, it's working. But I wouldn't consider that the best way to do it.

Jaypan We build websites

There's no doubt you are very

There's no doubt you are very knowledgeable in JS. I will take a look at ajaxPrepare() and do some performance tests. I always research many ways of doing the same thing and then make my own mind up which one is better, and I will do the same thing here. I would argue the best way to learn about timers and how best to use them, is to actually use them. Though your help is much appreciated.

Update: I don't want to come across argumentative, I appreciate the time you've taken to reply and help me. However, I've just taken a look in the uncompressed version of jQuery provided by the Devel module, the setTimeout() function is used a fair bit, which makes me think it's perfectly okay to use? For example, the delay() function is basically using setTimeout(). I can't see how it's so bad. Though I concede, I do need to do more research on it.

Another update (sorry): It appears the AJAX function within jQuery uses setTimeout anyway:

// Timeout checker
if ( s.async && s.timeout > 0 ) {
setTimeout(function() {
// Check to see if the request is still happening
if ( xhr && !requestDone ) {
onreadystatechange( "timeout" );
}
}, s.timeout);
}

Also, I've seen setTimeout used in core files, for example autocomplete.js. I can't see how it's bad practice to use it? Though I fear I'm rambling now.

No worries, and you aren't

No worries, and you aren't coming off as argumentative, just curious. It's good - that's what leads to being a good programmer! Anyways the best thing is that you find a solution that works for you, and you seem to coming along towards that. Good luck!!

Jaypan We build websites