How to jQuery Ajax Error Catch and Report - javascript

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!

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:
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>

Related

AJAX silently failing when loading script

I just notice a weird behavior when dynamically loading scripts with AJAX.
I intentionally have a misspelled code that throws an error when it is parsed. Even though the Chrome's console indeed shows the error, the AJAX fail handler is never called.
This is the function that I use to load files:
var load_source = function (path) {
/* Variable used to determine whether the request was successful */
var success = false,
failed;
$.ajax(
{
url: path,
async: false,
dataType: 'script',
method: 'GET'
}
).
done(function () {
success = true;
}).
fail(function (xhr, status, error) {
failed = error.stack ? error.stack : error;
});
if (failed)
{
throw new Error('Unable to load JS file {0}: {1}'.format(path, failed));
}
}
The only variable provided to the load_source function is "path", which value is a string with the location and name of such a file: "js/myFile.js".
The misspelled part (the part with a typo) of the script to load is this:
var f = function (arg) {
var param1 = 3,
param2, /* NOTICE the typo: there is a comma instead of a semicolon */
if (param1 > arg)
{
return true;
}
// And more code is coming next...
If I look at the Chrome's console, it shows the error:
Uncaught SyntaxError: Unexpected token if
So far the only way I can catch the error is with the window's onerror event.
So, if that is the only way to catch the error, could you tell me how to stop the "coding" flow, I mean, whenever I call load_source that throws an error (which is handled by the window's onerror function) I want the script to do nothing else:
load_source('js/myFile.js'); // This script will throw the "Uncaught SyntaxError: Unexpected token if" error.
// So I don't want the following code to be executed.
sum(4, 3);
mul(5, 5);
// ...
Can you guys tell me how make the fail event to be triggered?
I also tried with " $(document).ajaxError" but with the same results.
It's async stuff :) There is no way that the resource fetching (load_source(url)) is going to be finished before your sum() and mul() functions are executed.
The things you want to make dependable on the successful loading of your remote resource, should be placed in a callback, which is executed after success of the resource fetching.
UPDATE
Regarding the "aysnc: false" in your example, and mentioned in the
comments: this applies only to the $.ajax() function scope, not to
the parent function.
Also: is_empty is not part of the standard
library, I assume you have defined that function elsewhere?
The fail event is being triggered, it's just not stopping the rest of the JS from running. You'd be better off doing some thing like this:
if(load_source('js/myFile.js'))
{
sum(4, 3);
mul(5, 5);
}
Then removing your throw in your load_source() function and using return success;, like so:
var load_source = function (path) {
/* Variable used to determine whether the request was successful */
var success = false;
$.ajax(
{
url: path,
async: false,
dataType: 'script',
method: 'GET',
success: function(){
success = true;
},
error: function (xhr, status, error) {
console.log(error.stack ? error.stack : error);
}
}
);
return success;
}

How do I capture an aborted call or is setting the timeout to 0 correct?

I have a JavaScript client that works in Chrome and Firefox, but fails in IE. Looking at the network trace in the IE debugger it shows that multiple of the AJAX calls have been aborted.
I've been able to get around it by setting the timeout to 0. I'd like to know if this is the correct way to handle my requests being aborted? Basically what could go wrong?
My initial thought was that I should capture and resend on error, and if multiple resubmits do not result in a completed request, finally alert the user. I'd still like to know how to do this even if the setTimeout is the proper way to address my immediate issue.
Also the application will process an excel workbook of addresses, call a web service to add some data to them and then allow the user to download the enhanced file.
This is what I have so far, first in the app.js
var requestWithFeedback = function (args) {
$(".loader").removeClass('hidden');
var oldConfig = args.config || function () { };
args.config = function (xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + localStorage.token);
oldConfig(xhr);
extract: extract;
};
var deferred = m.deferred();
setTimeout(function () { // <== This solved in IE, but is this the way to handle this?
m.request(args).then(deferred.resolve, function(err){
if (err === "Invalid token!"){
m.route('/');
}
})}, 0);
$(".loader").addClass('hidden');
return deferred.promise;
}
From the model.js
app.MarkedAddresses.ProcessAddressBatch = function () {
var requestData = {
Addresses: app.MarkedAddresses.vm.addresses
}
return requestWithFeedback({
method: "POST"
, url: "API/server.ashx"
, data: requestData
, deserialize: function (value) { return value; }
})
.then(function (value) {
var responseJSON = $.parseJSON(value);
$.merge(app.MarkedAddresses.vm.results, responseJSON)
app.MarkedAddresses.vm.currentRecord(app.MarkedAddresses.vm.results.length);
app.MarkedAddresses.vm.progress(Math.max(app.MarkedAddresses.vm.progress(), ~~(app.MarkedAddresses.vm.currentRecord() / app.MarkedAddresses.vm.totalRecords() * 100)));
m.redraw(); //Force redraw for progress bar
return value;
},
function (error) { console.log(error) } // <== I thought error would show up here, but I never hit a breakpoint here.
);
}
Added loops
function process_wb(wb) {
app.MarkedAddresses.vm.results.length = 0;
$('.descending').removeClass("descending");
$('.ascending').removeClass("ascending");
app.MarkedAddresses.vm.progress(.1);
m.redraw();
var header = mapHeader(wb);
var addressJSON = to_json(wb, header);
app.MarkedAddresses.vm.totalRecords(addressJSON.length);
for (var i = 0; (i < addressJSON.length + 1) ; i += 1000) {
app.MarkedAddresses.vm.addresses = addressJSON.slice(i, Math.min(((i) + 1000), addressJSON.length));
app.MarkedAddresses.vm.response(new app.MarkedAddresses.vm.processAddressBatch());
}
}
Why isn't the error triggered in the section of the code?
It seems like I should add a deferred section here, but anything I've tried has been a syntax error.

how to find which method is causing the error during parallel ajax call

I am using $.when to make parallel ajax call to webapi controller and it works perfectly fine. The structure is given below,
$.when(GetDataFromMethodA(),GetDataFromMethodB(),GetDataFromMethodC())
.done(function (responseFromMethodA,responseFromMethodB, responseFromMethodC) {
if (responseFromMethodA != null) {
//do some action
}
if (responseFromMethodB != null) {
//do some action
}
if (responseFromMethodC != null) {
//do some action
}
}).fail(function (xhr, textStatus, errorThrown) {
//which method raised the exception?
});
Methods:
function GetDataFromMethodA() {
var Request = {};
Request.Code = name.find(':selected').val();
return $.ajax({
url: 'api/Data/GetCurrentView',
type: 'POST',
dataType: 'json',
data: Request
});
}
similarly, I have method B and C.
QUESTION:
There are situations where any one of the method fails and based on the failing method, I need to display appropriate message to the user. When anyone of the method fails, the exception is caught in the 'fail' section. But, how to find which method raised the exception?
If you use always instead of done, you can inspect whether the request succeeded with isResolved() or isRejected(), for instance:
$.when(GetDataFromMethodA(),GetDataFromMethodB(),GetDataFromMethodC())
.always(function (responseFromMethodA,responseFromMethodB, responseFromMethodC) {
if(responseFromMethodA.isRejected()) {
console.log('A did not work!');
}
if(responseFromMethodB.isRejected()) {
console.log('B did not work!');
}
// ...etc.
});

How to get default error of ajax call

Ok, what I am trying to do is alerting ajax errors according to its error codes and I have lots of ajax calls on website so I am using global ajax error handler function.
But what I want is if some ajax call already have default errors then show there not global.
$(document).ready(function(){
$(document).ajaxError(e,xhr,opt){
if(xhr.error){
//Don't do anything
} else {
alert('You have an error');
}
}
}
First Function :
$.ajax({
type:"post",
url:"page.php",
data:"name=mohit&lastname=bumb",
error:function(){
alert('error');
}
});
Second Function :
$.ajax({
type:"post",
url:"page.php",
data:"name=mohit&lastname=bumb",
});
So in 2nd case it should show You have an error and in first case just error
Yes you can, but you have to override jQuery default $.ajax methods. Check the following code that I used in one of my projects. Make sure you load the script just after jQuery.
My scenario was -
The web site had a lot of ajax partial views which had to check whether user is logged in or not. So I had to override jquery calls to check for it.
I also had to show a loader when any ajax call was made.
One more thing, some js are loaded by ajax, so I added a check whether the url is a .js file or normal url.
I have taken out the sensitive codes that were confidential for my project. The rest is here. This might help you.
$(document).ready(function () {
var oldjQuery = [];
oldjQuery["ajax"] = $.ajax;
oldjQuery["load"] = $.load;
var newOptions = [];
//override ajax
jQuery.ajax = function (options) {
newOptions["ajax"] = $.extend({}, options);
//override the success callback
newOptions["ajax"].success = function (data, textStatus, jqXhr) {
try {
if (options.url.indexOf('.js') <= -1) {
//this is a normal success call, do nothing
}
}
catch (err) {
//... my other codes, incase any error occurred
}
if (typeof options.success != 'undefined') {
//the ajax call has a success method specified, so call it
options.success(data, textStatus, jqXhr);
}
};
//override the error callback
newOptions["ajax"].error = function (jqXhr, textStatus, errorThrown) {
try {
if (options.url.indexOf('.js') <= -1) {
//this is a normal success call, do nothing
}
}catch (y) {
//... my other codes, incase any error occurred
}
//the ajax call has an error method specified, so call it
if (typeof options.error != 'undefined') {
options.error(jqXhr, textStatus, errorThrown);
}
};
return oldjQuery["ajax"](newOptions["ajax"]);
};
//override load function
jQuery.load = function (url, data, completeCallback, ignore) {
newOptions["load"].completeCallback = function (d, textStatus, jqXhr) {
try {
if (url.indexOf('.js') <= -1) {
//codes
}
} catch (err) {
try {
//codes
}catch (err2) {
}
}
if (typeof completeCallback != 'undefined') {
//call the default completed callback
completeCallback(d, textStatus, jqXhr);
}
};
return oldjQuery["load"](url, data, newOptions["load"].completeCallback);
};
});

WebSQL - Lost in Transaction

I am developing a web-app using both WebSQL and IndexedDB. IndexedDB works well in browsers that support it, and my WebSQL implementation works in most browsers, however on Android javascript execution seems to be halting inside db.transaction.
Here is my code. I receive no console logs after "Initiating transaction..." (and no further JS seems to execute).
window.store.getFile = function( filename, callback ) {
var db = window.store.websql.db;
console.log('Initiating transaction...');
db.transaction(function (tx) {
var filename2 = filename;
var query = 'SELECT * FROM file WHERE filename = "'+filename+'"';
console.log('Executing query:'+query);
tx.executeSql(query, [], function(t, r) {
if ( r.rows.length > 0 ) {
var len = r.rows.length, i;
for (i = 0; i < len; i++) {
var filename = r.rows.item(i).filename;
var data = r.rows.item(i).data;
if ( callback ) callback( true, filename2, data );
break; // Break here as only one result should be returned
}
} else {
if ( callback ) callback( false, filename2, null );
}
}, function(t, e) {
console.log(e);
if ( callback ) callback( false, filename2, null );
});
}, function(e) {
console.log('Database transaction error: '+e);
if ( callback ) callback( false, filename, null );
}, function() {
console.log('Transaction success');
});
console.log('Transaction initiated...');
};
(P.S. I also welcome any critique of my code...)
The error you are chasing is probably raised on the db level error handler (passed in openDatabase). Some android phones just don't accept null or undefined for that error handler the the symptoms are what you described. No error, no success just nothing...
Also: your success block should be in try/catch to prevent that error to bubble up automatically.
Also: note that the return value to success handler (false/true) will determine what happens when an error is thrown (rollback or just error without rollback).

Categories