Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to jQuery Ajax Error Catch and Report

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!

like image 863
Mr.roland Avatar asked Oct 15 '22 13:10

Mr.roland


1 Answers

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>
like image 159
CertainPerformance Avatar answered Oct 20 '22 05:10

CertainPerformance