Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new behavior for global ErrorHandler and uncaught errors from promises #27840

Closed
captaincaius opened this issue Dec 26, 2018 · 10 comments
Closed
Assignees
Labels
area: core Issues related to the framework runtime area: zones core: error handling freq1: low regression Indicates than the issue relates to something that worked in a previous version type: bug/fix
Milestone

Comments

@captaincaius
Copy link

🐞 bug report

Affected Package

Well I thought this was zone.js wrapping the errors, but I rolled zone back only zone and it didn't change.

Is this a regression?

Yes, the previous version in which this bug was not present was 5.x

Description

Previously, unhandled errors from promises were handed to a custom error handler unchanged. But now, it seems the error is wrapped and you need to unwrap it to get the real error that's sitting on the "rejection" property of the error.

The docs make no mention of this here https://angular.io/api/core/ErrorHandler and neither does any googling surprisingly.

I noticed this only after upgrading from angular 5 to angular 7 and one of my e2e tests that relied on my general error handler started failing.

The only workaround I found was to have something like this at the top of the error handler's handleError() method like this:

    while(error.rejection) {
      error = error.rejection;
    }

but...

  1. I'm quite sure there's a better way - now I have to type error as any
  2. I'm not sure if there are any other situations that will require unwrapping (e.g. from observables?).

Either way, if this was some kind of intentional feature, it's definitely a gotcha that I think should be mentioned (with the RIGHT way to handle it) in the doc link I pasted.

🔬 Minimal Reproduction

  1. follow the docs and implement a global error handler and try to handle unhandled requests uniformly - something like this:
@Injectable()
export class SpiffyErrorHandlerService implements ErrorHandler {
  constructor(private injector: Injector, private snackBar: MatSnackBar) {}
  public handleError(error: Error) {
    if (error instanceof HttpErrorResponse) {
      if (error.status === 401) {
        this.snackBar.open(
          'Oops.  It looks like you tried to access a logged-in resource while logged out.  Please log in again.',
          '',
          { duration: 3000 }
        );
    }
  }
}
  1. inject HttpClient

  2. await this.http.get('/some-404-returning-url').toPromise();

🔥 Exception or Error

EXPECTED: error handler works, gets the HttpErrorResponse, and shows the snackbar
ACTUAL: the HttpErrorResponse is on error.rejection instead of right on error.

🌍 Your Environment

Angular Version:





Angular CLI: 7.1.2
Node: 10.14.2
OS: linux x64
Angular: 7.1.3
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router, service-worker

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.10.7
@angular-devkit/build-angular     0.10.7
@angular-devkit/build-optimizer   0.10.7
@angular-devkit/build-webpack     0.10.7
@angular-devkit/core              7.0.7
@angular-devkit/schematics        7.1.2
@angular/cdk                      7.1.1
@angular/cli                      7.1.2
@angular/flex-layout              7.0.0-beta.19
@angular/material                 7.1.1
@ngtools/webpack                  7.0.7
@schematics/angular               7.1.2
@schematics/update                0.11.2
rxjs                              6.3.3
typescript                        3.1.6
webpack                           4.19.1
    

Anything else relevant?

@benlesh benlesh added area: common Issues related to APIs in the @angular/common package comp: misc labels Dec 28, 2018
@ngbot ngbot bot added this to the needsTriage milestone Dec 28, 2018
@mhagnumdw
Copy link
Contributor

Same behavior (problem?) here, but using Resolve.

If inside Resolve I make the call

this.http.get<Holiday>(`/some-404-returning-url/${id}`).subscribe();

the HttpErrorResponse in ErrorHandler is wrapped in error.rejection.

Versions:

Angular CLI: 6.2.3
Node: 8.11.1
Angular: 6.1.8

@pkozlowski-opensource pkozlowski-opensource added area: core Issues related to the framework runtime and removed area: common Issues related to APIs in the @angular/common package comp: misc labels Mar 8, 2019
@benlesh benlesh added area: zones regression Indicates than the issue relates to something that worked in a previous version type: bug/fix labels May 9, 2019
@benlesh
Copy link
Contributor

benlesh commented May 9, 2019

It appears to be an issue with the type of error coming out of Zone.js changing... Here's a small stackblitz repro

NOTE that it does this with any rejected promise.

@JiaLiPassion
Copy link
Contributor

JiaLiPassion commented Jun 11, 2019

Currently, in zone.js, unhandled promise reject will be wrapped as new Error with a rejection property which equals to the original error, I think we may need to handle this case in zone.js or angular, I will create a PR later to let zone.js return original error after zone.js is moved to angualr repo.

@JiaLiPassion
Copy link
Contributor

This one has been reverted because some test failed, will try in the next version

@didii
Copy link

didii commented Jan 27, 2020

This issue is still present in Angular 8 as far as I can see. We're currently on version 8.2.14. Is there a fix ready, was this issue forgotten or will it not be fixed by design?
In any case, what would definitely help if there was a type specifying the known properties (i.e. rejection, promise, zone, etc) so we'd at least know when the API changes. Is there any available?

@simeyla
Copy link

simeyla commented Jan 28, 2020

@didii just do this and then your handler is future proof

    handleError(wrapperError: any) 
    {
            const err = wrapperError.rejection ? wrapperError.rejection : wrapperError;

            // now handle 'err'

@AndresSp
Copy link

AndresSp commented Mar 5, 2020

The same problem here (Angular 8.2.14)

Maybe this could help someone:
I moved my error handler to an HTTP-interceptor, so I can handle the HttpErrorResponses without that wrapper

 export class ServerErrorInterceptor implements HttpInterceptor {
    constructor(private _errorRedirectedService: ErrorRedirectedService) {}

    public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{

      return next.handle(request).pipe(
        catchError((error: HttpErrorResponse) => {
          //HttpErrorResponse
          throw error //goes to the Global-Error-Handler
      }))
   }
 }

@captaincaius
Copy link
Author

Seeing as how this is still open, would the angular team welcome a little note in the docs simply explaining it?

@JiaLiPassion
Copy link
Contributor

JiaLiPassion commented Mar 5, 2020

the previous PR was reverted, I will create a new PR to fix this issue.

JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Mar 5, 2020
…ght promise rejection

Close angular#27840.

By default, `zone.js` wrap uncaught promise error and wrap it to a new Error object with some
additional information includes the value of the error and the stack trace.

Consider the following example:

```
Zone.current
  .fork({
    name: 'promise-error',
    onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
      console.log('caught an error', error);
      delegate.handleError(target, error);
      return false;
    }
}).run(() => {
  const originalError = new Error('testError');
  Promise.reject(originalError);
});
```

The `promise-error` zone catches a wrapped `Error` object whose `reject` property equals
to the `originalError`, and the message will be `Uncaught (in promise): testError...`.
You can disable this wrapping behavior by defining a global configuraiton
` __zone_symbol__DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION = true;` before importing `zone.js`.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Mar 5, 2020
…ght promise rejection

Close angular#27840.

By default, `zone.js` wrap uncaught promise error and wrap it to a new Error object with some
additional information includes the value of the error and the stack trace.

Consider the following example:

```
Zone.current
  .fork({
    name: 'promise-error',
    onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
      console.log('caught an error', error);
      delegate.handleError(target, error);
      return false;
    }
}).run(() => {
  const originalError = new Error('testError');
  Promise.reject(originalError);
});
```

The `promise-error` zone catches a wrapped `Error` object whose `reject` property equals
to the `originalError`, and the message will be `Uncaught (in promise): testError...`.
You can disable this wrapping behavior by defining a global configuraiton
` __zone_symbol__DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION = true;` before importing `zone.js`.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Mar 5, 2020
…ght promise rejection

Close angular#27840.

By default, `zone.js` wrap uncaught promise error and wrap it to a new Error object with some
additional information includes the value of the error and the stack trace.

Consider the following example:

```
Zone.current
  .fork({
    name: 'promise-error',
    onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
      console.log('caught an error', error);
      delegate.handleError(target, error);
      return false;
    }
}).run(() => {
  const originalError = new Error('testError');
  Promise.reject(originalError);
});
```

The `promise-error` zone catches a wrapped `Error` object whose `reject` property equals
to the `originalError`, and the message will be `Uncaught (in promise): testError...`.
You can disable this wrapping behavior by defining a global configuraiton
` __zone_symbol__DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION = true;` before importing `zone.js`.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Mar 6, 2020
…ght promise rejection

Close angular#27840.

By default, `zone.js` wrap uncaught promise error and wrap it to a new Error object with some
additional information includes the value of the error and the stack trace.

Consider the following example:

```
Zone.current
  .fork({
    name: 'promise-error',
    onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
      console.log('caught an error', error);
      delegate.handleError(target, error);
      return false;
    }
}).run(() => {
  const originalError = new Error('testError');
  Promise.reject(originalError);
});
```

The `promise-error` zone catches a wrapped `Error` object whose `reject` property equals
to the `originalError`, and the message will be `Uncaught (in promise): testError...`.
You can disable this wrapping behavior by defining a global configuraiton
` __zone_symbol__DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION = true;` before importing `zone.js`.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Mar 6, 2020
…ght promise rejection

Close angular#27840.

By default, `zone.js` wrap uncaught promise error and wrap it to a new Error object with some
additional information includes the value of the error and the stack trace.

Consider the following example:

```
Zone.current
  .fork({
    name: 'promise-error',
    onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
      console.log('caught an error', error);
      delegate.handleError(target, error);
      return false;
    }
}).run(() => {
  const originalError = new Error('testError');
  Promise.reject(originalError);
});
```

The `promise-error` zone catches a wrapped `Error` object whose `reject` property equals
to the `originalError`, and the message will be `Uncaught (in promise): testError...`.
You can disable this wrapping behavior by defining a global configuraiton
` __zone_symbol__DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION = true;` before importing `zone.js`.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Mar 8, 2020
…ght promise rejection

Close angular#27840.

By default, `zone.js` wrap uncaught promise error and wrap it to a new Error object with some
additional information includes the value of the error and the stack trace.

Consider the following example:

```
Zone.current
  .fork({
    name: 'promise-error',
    onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
      console.log('caught an error', error);
      delegate.handleError(target, error);
      return false;
    }
}).run(() => {
  const originalError = new Error('testError');
  Promise.reject(originalError);
});
```

The `promise-error` zone catches a wrapped `Error` object whose `reject` property equals
to the `originalError`, and the message will be `Uncaught (in promise): testError...`.
You can disable this wrapping behavior by defining a global configuraiton
` __zone_symbol__DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION = true;` before importing `zone.js`.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Mar 8, 2020
…ght promise rejection

Close angular#27840.

By default, `zone.js` wrap uncaught promise error and wrap it to a new Error object with some
additional information includes the value of the error and the stack trace.

Consider the following example:

```
Zone.current
  .fork({
    name: 'promise-error',
    onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
      console.log('caught an error', error);
      delegate.handleError(target, error);
      return false;
    }
}).run(() => {
  const originalError = new Error('testError');
  Promise.reject(originalError);
});
```

The `promise-error` zone catches a wrapped `Error` object whose `reject` property equals
to the `originalError`, and the message will be `Uncaught (in promise): testError...`.
You can disable this wrapping behavior by defining a global configuraiton
` __zone_symbol__DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION = true;` before importing `zone.js`.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Mar 9, 2020
…ght promise rejection

Close angular#27840.

By default, `zone.js` wrap uncaught promise error and wrap it to a new Error object with some
additional information includes the value of the error and the stack trace.

Consider the following example:

```
Zone.current
  .fork({
    name: 'promise-error',
    onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
      console.log('caught an error', error);
      delegate.handleError(target, error);
      return false;
    }
}).run(() => {
  const originalError = new Error('testError');
  Promise.reject(originalError);
});
```

The `promise-error` zone catches a wrapped `Error` object whose `reject` property equals
to the `originalError`, and the message will be `Uncaught (in promise): testError...`.
You can disable this wrapping behavior by defining a global configuraiton
` __zone_symbol__DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION = true;` before importing `zone.js`.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Mar 9, 2020
…ght promise rejection

Close angular#27840.

By default, `zone.js` wrap uncaught promise error and wrap it to a new Error object with some
additional information includes the value of the error and the stack trace.

Consider the following example:

```
Zone.current
  .fork({
    name: 'promise-error',
    onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
      console.log('caught an error', error);
      delegate.handleError(target, error);
      return false;
    }
}).run(() => {
  const originalError = new Error('testError');
  Promise.reject(originalError);
});
```

The `promise-error` zone catches a wrapped `Error` object whose `rejection` property equals
to the original error, and the message will be `Uncaught (in promise): testError....`,
You can disable this wrapping behavior by defining a global configuraiton
`__zone_symbol__DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION = true;` before importing `zone.js`.
AndrewKushnir pushed a commit to AndrewKushnir/angular that referenced this issue Mar 12, 2020
…ght promise rejection

Close angular#27840.

By default, `zone.js` wrap uncaught promise error and wrap it to a new Error object with some
additional information includes the value of the error and the stack trace.

Consider the following example:

```
Zone.current
  .fork({
    name: 'promise-error',
    onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
      console.log('caught an error', error);
      delegate.handleError(target, error);
      return false;
    }
}).run(() => {
  const originalError = new Error('testError');
  Promise.reject(originalError);
});
```

The `promise-error` zone catches a wrapped `Error` object whose `rejection` property equals
to the original error, and the message will be `Uncaught (in promise): testError....`,
You can disable this wrapping behavior by defining a global configuraiton
`__zone_symbol__DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION = true;` before importing `zone.js`.
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Apr 16, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime area: zones core: error handling freq1: low regression Indicates than the issue relates to something that worked in a previous version type: bug/fix
Projects
None yet
9 participants