75

We have a pattern for resolving promises in our Angular app that has served us well up until Angular 1.6.0:

    resource.get().$promise
        .then(function (response) {
        // do something with the response
        }, function (error) {
            // pass the error the the error service
            return errorService.handleError(error);
        });

And here is how we are triggering the error in Karma:

    resourceMock.get = function () {
        var deferred = $q.defer();
        deferred.reject(error);
        return { $promise: deferred.promise };
    };

Now, with the update to 1.6.0, Angular is suddenly complaining in our unit tests (in Karma) for rejected promises with a "Possibly unhandled rejection" error. But we are handling the rejection in the second function that calls our error service.

What exactly is Angular looking for here? How does it want us to "handle" the rejection?

Mistalis
  • 17,793
  • 13
  • 73
  • 97
Groucho
  • 1,055
  • 1
  • 7
  • 11
  • 1
    I noticed this in our code base as well. Weirdly, running the suite with the chrome launcher works fine. PhantomJS is the one complaining. – Mario Tacke Dec 19 '16 at 19:58
  • If you get this and didn't just upgrade, here's how you can check your Angular version: http://stackoverflow.com/questions/16017699/how-can-i-check-which-version-of-angular-im-using – hubatish May 08 '17 at 18:16
  • The accepted answer recommends hiding the error.The duplicate suggests a more robust alternative with [this answer](https://stackoverflow.com/a/45368544/5535245). – georgeawg Aug 14 '18 at 01:16

11 Answers11

66

Try adding this code to your config. I had a similar issue once, and this workaround did the trick.

app.config(['$qProvider', function ($qProvider) {
    $qProvider.errorOnUnhandledRejections(false);
}]);
dashrb
  • 952
  • 4
  • 10
Cengkuru Michael
  • 4,590
  • 1
  • 33
  • 33
25

The code you show will handle a rejection that occurs before the call to .then. In such situation, the 2nd callback you pass to .then will be called, and the rejection will be handled.

However, when the promise on which you call .then is successful, it calls the 1st callback. If this callback throws an exception or returns a rejected promise, this resulting rejection will not be handled, because the 2nd callback does not handle rejections in cause by the 1st. This is just how promise implementations compliant with the Promises/A+ specification work, and Angular promises are compliant.

You can illustrate this with the following code:

function handle(p) {
    p.then(
        () => {
            // This is never caught.
            throw new Error("bar");
        },
        (err) => {
            console.log("rejected with", err);
        });
}

handle(Promise.resolve(1));
// We do catch this rejection.
handle(Promise.reject(new Error("foo")));

If you run it in Node, which also conforms to Promises/A+, you get:

rejected with Error: foo
    at Object.<anonymous> (/tmp/t10/test.js:12:23)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)
    at bootstrap_node.js:509:3
(node:17426) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: bar
Louis
  • 146,715
  • 28
  • 274
  • 320
  • Good points! Yes, the intent is to handle rejection that occurs before the call to .then. The code that is triggering this rejection (and which worked for Angular 1.5.9) has been added to the original question. – Groucho Dec 09 '16 at 16:48
  • 3
    How to actually fix asked pattern code? – vip Dec 13 '16 at 14:05
  • 6
    Ok, fix is like $promise.then(success).catch(error), where catch catches all errors, more in "Note" section at [migration](https://docs.angularjs.org/guide/migration#migrate1.5to1.6-ng-services-$http) – vip Dec 13 '16 at 19:00
  • 1
    this really should be the *correct* answer – tcmoore Feb 12 '18 at 21:22
  • 2
    What if your function returns a rejected promise by design? Similar to the way $http.get() returns a promise that by design is rejected if the request fails. I have functions which return promises, and in certain cases they can return $q.reject(err), it seems that angular doesn't like this unless I clobber the warnings? – Ryan Silva Mar 29 '18 at 17:20
22

The first option is simply to hide an error with disabling it by configuring errorOnUnhandledRejections in $qProvider configuration as suggested Cengkuru Michael

BUT this will only switch off logging. The error itself will remain

The better solution in this case will be - handling a rejection with .catch(fn) method:

resource.get().$promise
    .then(function (response) {})
    .catch(function (err) {});

LINKS:

Sibiraj
  • 4,486
  • 7
  • 33
  • 57
Andrii Verbytskyi
  • 7,155
  • 3
  • 47
  • 38
19

Found the issue by rolling back to Angular 1.5.9 and rerunning the test. It was a simple injection issue but Angular 1.6.0 superseded this by throwing the "Possibly Unhandled Rejection" error instead, obfuscating the actual error.

Groucho
  • 1,055
  • 1
  • 7
  • 11
  • This is a big problem for me currently as I'm migrating a code base and replacing an old injected dependency. Did you find a way to prevent that obfuscation and get it to throw the real error? – ChrisJ Apr 17 '17 at 13:07
  • The top answer above will fix it in running code, but I couldn't get it to throw the error in Karma without rolling back to Angular 1.5.9. – Groucho Apr 17 '17 at 18:47
7

To avoid having to type additional .catch(function () {}) in your code in multiple places, you could add a decorator to the $exceptionHandler.

This is a more verbose option than the others but you only have to make the change in one place.

angular
    .module('app')
    .config(configDecorators);

configDecorators.$inject = ["$provide"];
function configDecorators($provide) {

    $provide.decorator("$exceptionHandler", exceptionHandler);

    exceptionHandler.$inject = ['$delegate', '$injector'];
    function exceptionHandler($delegate, $injector) {
        return function (exception, cause) {

            if ((exception.toString().toLowerCase()).includes("Possibly unhandled rejection".toLowerCase())) {
                console.log(exception); /* optional to log the "Possibly unhandled rejection" */
                return;
            }
            $delegate(exception, cause);
        };
    }
};
Urielzen
  • 476
  • 7
  • 17
5

You could mask the problem by turning off errorOnUnhandledRejections, but the error says you're needing to "handle a possible rejection" so you just need to add a catch to your promise.

resource.get().$promise
    .then(function (response) {
    // do something with the response
    }).catch(function (error)) {
        // pass the error to the error service
        return errorService.handleError(error);
    });

Reference: https://github.com/angular-ui/ui-router/issues/2889

Sylvain Rodrigue
  • 4,751
  • 5
  • 53
  • 67
giselleghadyani
  • 182
  • 2
  • 7
3

Please check the answer here:

Possibly unhandled rejection in Angular 1.6

This has been fixed with 316f60f and the fix is included in the v1.6.1 release.

Piotr Pradzynski
  • 4,190
  • 5
  • 23
  • 43
2

I have observed the same behavior during test execution. It is strange that on production code works fine and fails only on tests.

Easy solution to make your tests happy is to add catch(angular.noop) to your promise mock. In case of example above it should looks like this:

resourceMock.get = function () {
    var deferred = $q.defer();
    deferred.reject(error);
    return { $promise: deferred.promise.catch(angular.noop) };
};
2

I was also facing the same issue after updating to Angular 1.6.7 but when I looked into the code, error was thrown for $interval.cancel(interval); for my case

My issue got resolved once I updated angular-mocks to latest version(1.7.0).

Dilip
  • 628
  • 2
  • 10
  • 23
1

I had this same notice appear after making some changes. It turned out to be because I had changed between a single $http request to multiple requests using angularjs $q service.

I hadn't wrapped them in an array. e.g.

$q.all(request1, request2).then(...) 

rather than

$q.all([request1, request2]).then(...)

I hope this might save somebody some time.

Paul Rooney
  • 20,879
  • 9
  • 40
  • 61
0

It may not be your speficic situation, but I had a similar problem.

In my case, I was using angular-i18n, and getting the locale dictionary asynchronously. The problem was that the json file it was getting was incorrectly indented (mixing spaces and tabs). The GET request didn't failed.

Correcting the indentation solved the problem.

monstercode
  • 944
  • 6
  • 25