5

In what way to get the function caller of ajaxError event produced for js error reporting?

I have built a js error repo app with jQuery and I can handle normal js errors occurred globally, but I have problems on ajax errors. I can get the line number of error when it is a normal error! I tried to catch them with one of global ajax handlers "ajax error", but I am not sure how to get line number of that ajax caller or caller name.

please look at the bottom part!

const error_log_url = '/log';
const errorPost = function (data) {
    $.ajax({
         url: error_log_url,
         type: 'post',
         data: data,
         success: function (res) {
             console.log(res)
         }, error: function (res) {
            console.log(res)
         }
     })
 }
 window.addEventListener('error', function (e) {

     let params = {
         message: e.message || "Exception Handler",
         url: e.filename || "",
         lineno: e.lineno || 0,
         colno: e.colno || 0
     }

     errorPost(params)
 }, true);

 // wrap function for new error stack with adding event to certain element
 window.wrap = function (func) {
    // make sure you only wrap the function once
    if (!func._wrapped) {
        func._wrapped = function () {
            try {
                func.apply(this, arguments);
            } catch (exception) {
                throw exception
            }
        }
    }
    return func._wrapped;
 }

 // override add & remove event listeners with above wrap function
 let addEvenListener = window.EventTarget.prototype.addEventListener;
 window.EventTarget.prototype.addEventListener = function (event, callback, bubble) {
    addEvenListener.call(this, event, wrap(callback), bubble);
 }

 let removeEventLister = window.EventTarget.prototype.removeEventListener;
 window.EventTarget.prototype.removeEventListener = function (event, callback, bubble) {
    removeEventLister.call(this, event, callback._wrapped || callback, bubble);
 }

 $(document).ajaxError(function( event, jqxhr, settings, thrownError ) {

     // please look at here, how can I get the caller name that produced this error!
     console.log(arguments.callee.caller)

     if (settings.url != error_log_url)
         errorPost({
             message: event.type,
             filename: event.currentTarget.location.origin + settings.url
         })


 });

console.log(arguments.callee.caller) this prints out null.

you see, I can get much more info from ErrorEvent, but I can not get detailed info like line number from ajaxError event!

mars328
  • 573
  • 2
  • 8
  • 26
  • @CertainPerformance, yeah, right, do you know how the google browser knows which line caused error. i think it is better to follow the way browsers do! – mars328 Jun 27 '19 at 21:26
  • Thanks for clarification. For a specific example, do you want to, eg, get the name `errorPost` (and line number of the `$.ajax` there) if its ajax throws, inside your `ajaxError` handler, is that it? – CertainPerformance Jun 27 '19 at 21:41
  • @CertainPerformance, yes, exactly, not just errorPost but name or line num of all functions that would possibly produce ajax errors! You got a point! – mars328 Jun 27 '19 at 22:02

1 Answers1

3

Unfortunately, it looks like there is no global event for network errors.

There is a hacky way to figure it out, though - if you attach a function to the ajaxSend method, which runs when a request is sent, you can throw an error immediately, then catch it and examine the stack to figure out the caller. Then, put the appropriate stack line into a WeakMap which can be examined later, indexed by the jqXHR object. Afterwards, if the request fails, in the ajaxError handler, use its jqXHR object to look up the stack in the WeakMap. For example:

$(document).ajaxError(function(event, jqxhr, settings, thrownError) {
  console.log(stacksByXHR.get(jqxhr));
});
const stacksByXHR = new WeakMap();
$(document).ajaxSend((event, jqXHR) => {
  try {
    throw new Error();
  } catch({ stack }) {
    let callCountNonJquery = 0;
    const foundCall = stack
      .split('\n')
      .slice(1) // Remove the top "Error" line, contains no information
      .find(line => {
        if (line.includes('jquery')) {
          return false;
        }
        callCountNonJquery++;
        // First call would be the thrown error above
        // Second call is the $.ajax initiator
        if (callCountNonJquery === 2) {
          return true;
        }
      });
    stacksByXHR.set(jqXHR, foundCall);
  }
});
$.ajax('/DoesNotExist');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

On my machine, this shows me

at https://stacksnippets.net/js:40:3

which corresponds to the $.ajax('/DoesNotExist'); line:

enter image description here

If the $.ajax is inside a function, the function name will be visible in the stack too, for example:

$(document).ajaxError(function(event, jqxhr, settings, thrownError) {
  console.log(stacksByXHR.get(jqxhr));
});
const stacksByXHR = new WeakMap();
$(document).ajaxSend((event, jqXHR) => {
  try {
    throw new Error();
  } catch({ stack }) {
    let callCountNonJquery = 0;
    const foundCall = stack
      .split('\n')
      .slice(1) // Remove the top "Error" line, contains no information
      .find(line => {
        if (line.includes('jquery')) {
          return false;
        }
        callCountNonJquery++;
        // First call would be the thrown error above
        // Second call is the $.ajax initiator
        if (callCountNonJquery === 2) {
          return true;
        }
      });
    stacksByXHR.set(jqXHR, foundCall);
  }
});
function myFunctionWhichRunsAjax() {
  $.ajax('/DoesNotExist');
}
myFunctionWhichRunsAjax();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • what if there are many ajax calls periodically with something like setTimeout producing error, the weakmap can be out of memory? – mars328 Jun 28 '19 at 00:11
  • No, because it's a `WeakMap`, not a `Map` - if nothing other than the `WeakMap` has a remaining reference to the `jqXHR`, it will be removed from the `WeakMap`, so as long as *other* parts of your code don't retain a reference to it, memory usage shouldn't pile up in the long run. – CertainPerformance Jun 28 '19 at 00:14
  • perfect, thanks a lot, by the way is there any better suggestion for that kind of problem as I said for something like "setTimeout"? It produces so many errors if I call ajax periodically, and they are from same url, the db is full of same errors. I would like to save maybe up to 5 same errors. – mars328 Jun 28 '19 at 00:21
  • 1
    You could make another object indexed by the error throw location, whose values are the number of times that location has thrown (perhaps concatenated with the name of the error, or something like that). On every error, increment the appropriate property value by 1, and if that value is greater than 5, ignore the error (don't send it to `error_log_url`) – CertainPerformance Jun 28 '19 at 00:23