sylvain
sylvain

Reputation: 1971

Get the full call stack trace of $http calls

Let's say someone wrote a method like this in a file called app.js trying to peform an XHR request angainst a non existing url:

app.controller('MainCtrl', function($scope,$http) {
  $scope.send = function() {
    $http.get('http://run.plnkr.co/thisIs404');
  };
});

I can see an error regarding URL http://run.plnkr.co/thisis404 in console and network panel :

error in console

To debug this I want to find quickly where this XHR call was made in sources (ie find the app.js file) :

So I enable in chrome dev tools :

Debugger actually stops on XHR request, but the call stack only displays references to angular.js "core" files : no reference to app.js anywhere to be found.

google chrome call stack

I tried this with chromium 36 and chrome 35. Only solution : search for the wrong URL in the whole code base (which in some case may be hard to do).

With vanilla XHR requests (ie without angular), XHR debug call stack displays the XHR call in app.js (which is easier to debug in this case) :

enter image description here

Full example here : http://plnkr.co/edit/lnCRpv?p=preview

[EDIT] As i've been asked : Angular.js is not minified in my tests.

Upvotes: 18

Views: 9185

Answers (3)

S.Klechkovski
S.Klechkovski

Reputation: 4045

One option that came in my mind is to create a module for $http debugging that you can add it as a dependency in your main module whenever you need to debug $http calls. There a decorator for the $http service can be registered that will simply log the arguments of the call a forward it to the $http service. There a breakpoint can be set too.

I have created a simple working example here. I hope it will help.

Example $http logger decorator implementation:

var httpDebugging = angular.module('http-debugging', []);

httpDebugging.decorator('$http', function ($delegate) {
  var debugAware = function (fnCallback) {
    return function () {
        var result = fnCallback.apply(this, arguments);    
        console.log('$http decorator: ', arguments);
        return result;
    };   
  }; 

  for(var prop in $delegate) {
    if (angular.isFunction($delegate[prop])) {
        $delegate[prop] = debugAware($delegate[prop]);
    }
  }

  return $delegate;
});

Upvotes: 0

Florian Margaine
Florian Margaine

Reputation: 60787

So, you see, this issue is mostly because angular's $http sucks. Sorry about that.

Let's try to use the bluebird library, because it provides long stack traces.

Promise.longStackTraces();
Promise.resolve($http.get('...'));

You get the following stack trace:

Possibly unhandled Error: [object Object]
    at Promise$_rejectFromThenable (http://cdn.jsdelivr.net/bluebird/1.2.4/bluebird.js:4736:52)
    at wrappedErrback (https://code.angularjs.org/1.3.0-beta.5/angular.js:11334:78)
    at wrappedErrback (https://code.angularjs.org/1.3.0-beta.5/angular.js:11334:78)
    at wrappedErrback (https://code.angularjs.org/1.3.0-beta.5/angular.js:11334:78)
    at https://code.angularjs.org/1.3.0-beta.5/angular.js:11467:76
    at Scope.$eval (https://code.angularjs.org/1.3.0-beta.5/angular.js:12418:28)
    at Scope.$digest (https://code.angularjs.org/1.3.0-beta.5/angular.js:12230:31)
    at Scope.$apply (https://code.angularjs.org/1.3.0-beta.5/angular.js:12522:24)
    at done (https://code.angularjs.org/1.3.0-beta.5/angular.js:8207:45)
    at completeRequest (https://code.angularjs.org/1.3.0-beta.5/angular.js:8412:7) 

(Plunker here.)

The most important line is the first: Possibly unhandled Error: [object Object].

Yep. An object is thrown, not a real Error object, with the stack property attached to it. For the reference, here is how to throw an error and keep its stack along with it: https://github.com/Ralt/newerror/blob/master/index.js

So, how to fix this? It depends on several decisions:

  • Do you want to add a proper Promise lib that enables long stack traces?
  • Do you want to use another xhr lib that throws correct errors?

If you want to add a real Promise lib, use bluebird. AFAIK, it is one of the few that provides long stack traces, and it is the fastest one out there.

For a proper xhr lib that throws real errors, I'm afraid you're out of luck there. Writing a custom one with the support for browsers you want isn't really hard though. With no IE8 support, this works (with bluebird):

function xhr(url) {
    return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.onload = function() {
            resolve(xhr.responseText);
        };
        xhr.onerror = reject;
        xhr.open('GET', url);
        xhr.send();
    });
}

(Plunker here.)

As you can see, the stack trace is informative:

correct stack trace

Upvotes: 11

sylvain
sylvain

Reputation: 1971

XHR requests are stacked in $http.pendingRequests array and are send later. Which is why you can't find a direct linked between where $http is called and where the actual XHR request is made.

If you want to know which function called $http you have to set a breakpoint in $http function.

It kinds of defeat the whole "XHR breakpoints" purpose in my opinion.

Upvotes: 1

Related Questions