Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(platform-server): propagate errors from lifecycle hooks when utilizing platform-server #55787

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

Closed
wants to merge 1 commit into from

Conversation

alan-agius4
Copy link
Contributor

@alan-agius4 alan-agius4 commented May 14, 2024

In previous versions, if an error occurred within the lifecycle hooks, the promises for renderModule and renderApplication were not rejected. This could result in serving a "broken" page during Server-Side Rendering (SSR), or the build erroneously being marked as successful during Static Site Generation (SSG).

Fixes #33642

@alan-agius4 alan-agius4 added area: core Issues related to the framework runtime target: rc This PR is targeted for the next release-candidate labels May 14, 2024
@ngbot ngbot bot added this to the Backlog milestone May 14, 2024
@alan-agius4 alan-agius4 added the area: server Issues related to server-side rendering label May 14, 2024
@alan-agius4 alan-agius4 requested a review from AndrewKushnir May 14, 2024 06:56
@alan-agius4 alan-agius4 marked this pull request as ready for review May 14, 2024 06:57
@alan-agius4 alan-agius4 marked this pull request as draft May 14, 2024 08:19
@alan-agius4 alan-agius4 force-pushed the render-error-server branch from 81d2845 to cdee9c8 Compare May 14, 2024 12:18
@alan-agius4 alan-agius4 changed the title fix(core): propagate synchronous errors from lifecycle hooks when utilizing platform-server fix(platform-server): propagate errors from lifecycle hooks when utilizing platform-server May 14, 2024
@alan-agius4 alan-agius4 force-pushed the render-error-server branch 11 times, most recently from f4be47b to 88b383e Compare May 15, 2024 08:06
…izing platform-server

In previous versions, if an error occurred within the lifecycle hooks, the promises for `renderModule` and `renderApplication` were not rejected. This could result in serving a "broken" page during Server-Side Rendering (SSR), or the build erroneously being marked as successful during Static Site Generation (SSG).

Fixes angular#33642
@alan-agius4 alan-agius4 force-pushed the render-error-server branch from 88b383e to 8a21cf8 Compare May 15, 2024 11:46
@pawelfras
Copy link
Contributor

pawelfras commented May 20, 2024

Hey @alan-agius4,
I appreciate your work and the fact that the Angular Core team has started working on Error handling in SSR/SSG! I would like to ask about two topics:

  • It may be beyond the scope of this PR, but how to trade HTTP errors from the backend? Make a custom HTTP interceptor that catches all backend HTTP errors and then calls ErrorHandler.handleError(HTTP error response). Such a kind of errors should also be handled so that in the end the user doesn't receive with high certainty malformed HTML with wrong status 200
  • What is the advantage of emitting a rejection of the promise whenStableWithoutError immediately, not only when the app is stable? In such a scenario, HTTP requests may still exist as 'stabilisation' is in progress. In both SSR and SSG.

I hope you don't mind that I added this comment directly to this PR.

Best regards!

@AndrewKushnir AndrewKushnir added target: patch This PR is targeted for the next patch release and removed target: rc This PR is targeted for the next release-candidate labels May 22, 2024
@Platonn
Copy link
Contributor

Platonn commented May 31, 2024

Hi @alan-agius4

Did you consider making the ErrorInterceptor public and extendable? Our use case is to capture the application's state (e.g. get data from the ngrx store via DI, or any other context from DI) and attach it as a context to the propagated error. Thanks to this context, we'll be able to better handle this error in the ExpressJS layer, where the Angular DI context is unavailable. For example, based on the additional context attached to the propagated error object, in ExpressJS we can set an appropriate HTTP error status code for the response to the client, e.g. 404 vs 500.

The BEFORE_APP_SERIALIZED is the hook of the "last moment" allowing to access Angular DI and modify the final HTML markup (e.g. attaching the TransferState).

Analogically, we need the hook of "last moment" allowing to access DI and modify the final error (or returning a brand new error object - use case: return a custom "wrapper Error object" with the cause property pointing to the original error)

If you don't want to expose in the public API the whole ErrorInterceptor, we could at least expose the injection token hook in @angular/platform-server, e.g. new InjectionToken<(error: unknown) => unknown>, maybe named as BEFORE_ERROR_PROPAGATED.

@Platonn
Copy link
Contributor

Platonn commented Oct 1, 2024

Hi @alan-agius4,

Thank you so much for all the hard work you put into improving Angular SSR in various fields. And thank you for your efforts on this PR. I truly believe Async Error Handling is a significant improvement for Angular SSR/Prerendering.

I noticed the PR was recently closed, and I understand that there might be higher priorities on the Angular team's roadmap or perhaps a different approach might be in the works.

If it's a matter of having too many other duties, I'd be more than happy to help and contribute in any way I can. Please feel free to let me know how we could possibly collaborate to make Angular SSR Async Error Handling happen 😊

Thanks again!

Comment on lines +66 to +88
/**
* Intecept unhandled promise rejections.
* This is crucial to prevent server crashes in scenarios where Zone.js is not utilized, and to avoid application
* build to succeed incorrectly during SSG when async errors occurring within life-cycle hooks, listeners and other.
* The framework currently lacks proper handling of async methods, leading to unhandled promise rejections. This issue needs to be rectified in future.
*/
private interceptUnhandledRejection(): void {
if (typeof process !== 'undefined') {
const listener: (reason: unknown, promise: Promise<unknown>) => void = (reason) =>
this.onError(reason);
process.on('unhandledRejection', listener);
this.unlistenUnhandledRejection = () =>
process.removeListener('unhandledRejection', listener);
} else if ('addEventListener' in globalThis) {
const listener = (event: PromiseRejectionEvent) => {
this.onError(event.reason);
event.preventDefault();
};

addEventListener('unhandledrejection', listener);
this.unlistenUnhandledRejection = () => removeEventListener('unhandledrejection', listener);
}
}
Copy link
Contributor

@Platonn Platonn Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @alan-agius4

The framework currently lacks proper handling of async methods, leading to unhandled promise rejections. This issue needs to be rectified in future.

This is a serious limitation, isn't it? If I understand correctly, without ZoneJS we're not able to "wrap" the execution context of the rendered app and associate errors happneing asynchronously within this context with the appropriate ErrorInterceptor.

In other words, if I render simultaneously 2 Angular apps for 2 incoming requests on the same ExpressJS process, then - if we add a global listener addEventListener('unhandledrejection', ...) 2 times - in each ErrorInterceptor of each app. Then in case of an async error in just one of those 2 apps, then both globally-registered listeners will fire, and both ErrorInterceptors onError() will be invoked, which would be a bug.

Perhaps we could address this problem by wrapping each rendering of each app via a native NodeJS Async Context. That said, I didn't explore this option yet - it's just an idea from top of my head.

@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 Nov 10, 2024
Platonn referenced this pull request in angular/angular-cli Apr 14, 2025
Implement the `attachNodeGlobalErrorHandlers` function to handle 'unhandledRejection' and 'uncaughtException' events in Node.js. This function logs errors to the console, preventing unhandled errors from crashing the server. It is particularly useful for zoneless apps, ensuring error handling without relying on zones.

Closes angular/angular#58123
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: server Issues related to server-side rendering target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

renderModuleFactory does not reject when error is thrown in application
4 participants