Skip to content

zone.js promises returning Unhandled Promise Rejection error even with catch blocks #51328

Closed

Description

@ioancris

Which @angular/* package(s) are the source of the bug?

zone.js

Is this a regression?

No

Description

I've hit the same issue as described in #31680 and both this and that ticket have same root cause detailed bellow. Since I cannot reopened old issue I've created a new one. That older ticket was closed with a fix that was for a separate issue and the fix was not relevant or helped in anyway.

Repro

Following simplified code will emit unhandled promise rejection even tho there is a catch handler. This happens only when zone.js is loaded. Without zone.js, all majors browsers (Chrome, Safari, Firefox) don't report anything in this case.

onunhandledrejection = (e) => { console.log("Unhandled promise rejection", e) }

(async function() {
    return Promise.reject(1);
})().catch(e => {});
// OR
(async function() {
    await Promise.reject(2);
})().catch(e => {});

Using native Promise.reject will not emit unhandled rejection.

Root cause:

zone.js schedules the reporting of unhandled rejections on the microtask queue as soon as the promise is rejected. In the case of Promise.reject it schedules as part of the reject call.
JS async function calls .then on the returned inner promise (Promise.reject) on the next microtask tick. This is defined in the Ecmascript spec.
zone.js will end up running the code to report unhandled rejections before JS async function has a chance to call then and as such zone.js ends emiting unhandled rejection.

All major browsers check if unhandled rejections exists at the end of the microtask checkpoint (i.e. after draining the queue) and if needed will schedule on normal queue a task (or what you call a macro task) to process&report unhandled rejections. This allows sufficient time for the async infrastructure to hook to the returned promise. In Chrome and Safari it even allows sufficient time to add catch handler in a callback run by setTimeout of delay 0.

Chrome:
https://github.com/chromium/chromium/blob/89e00b6b959f5efb4bd7eec468973badb3093a3b/third_party/blink/renderer/platform/scheduler/common/event_loop.cc#L108
https://github.com/chromium/chromium/blob/89e00b6b959f5efb4bd7eec468973badb3093a3b/third_party/blink/renderer/bindings/core/v8/rejected_promises.cc#L248-L252
Firefox:
https://github.com/mozilla/gecko-dev/blob/77dd6aa3810610949a5ff925e24de2f8c11377fd/xpcom/base/CycleCollectedJSContext.cpp#L473-L480
Safari:
https://github.com/WebKit/WebKit/blob/75a3eb3a5fe3dce1867d880faf785137963b234a/Source/WebCore/dom/Microtasks.cpp#L107
https://github.com/WebKit/WebKit/blob/75a3eb3a5fe3dce1867d880faf785137963b234a/Source/WebCore/dom/RejectedPromiseTracker.cpp#L142-L144

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

Unhandled Promise rejection: 1 ; Zone: <root> ; Task: null ; Value: 1 undefined
Unhandled Promise rejection: 2 ; Zone: <root> ; Task: null ; Value: 2 undefined
Unhandled promise rejection PromiseRejectionEvent {isTrusted: true,  reason: 1, type: 'unhandledrejection' ...}
Uncaught (in promise) 1
Unhandled promise rejection PromiseRejectionEvent {isTrusted: true,  reason: 2, type: 'unhandledrejection' ...}
Uncaught (in promise) 2

Please provide the environment you discovered this bug in (run ng version)

No response

Anything else?

No response

Activity

  1. added this to the needsTriage milestone on Aug 11, 2023
  2. JoostK commented on Aug 11, 2023

    @JoostK
    Member

    Seems related to #47562 (specific to finally) and open PR #49589.

  3. ioancris commented on Aug 11, 2023

    @ioancris
    Author

    That PR won't fix this issue.
    This issue has a different root cause.

  4. ioancris commented on Aug 14, 2023

    @ioancris
    Author

    Here is a snipper of code that can workaround the above issue in zone.js

    (function fixzone() {
        const unhandledPromiseRejectionHandler = Zone['__zone_symbol__unhandledPromiseRejectionHandler'];    
        const pendingRejections = new Map();
        Zone['__zone_symbol__unhandledPromiseRejectionHandler'] = function (e) {
            const timer = setTimeout(() => {
                pendingRejections.delete(e.promise);
                unhandledPromiseRejectionHandler(e);
            });
            pendingRejections.set(e.promise, timer);
        }
        const rejectionHandledHandler = Zone['__zone_symbol__rejectionHandledHandler'];
        Zone['__zone_symbol__rejectionHandledHandler'] = function (e) {
            const timer = pendingRejections.get(e.promise);
            if (typeof timer === "number") {
                clearTimeout(timer);
                pendingRejections.delete(e.promise);
            }
            rejectionHandledHandler(e);
        }
        // optional function to remove the fix.
        return () => {
            Zone['__zone_symbol__unhandledPromiseRejectionHandler'] = unhandledPromiseRejectionHandler;
            Zone['__zone_symbol__rejectionHandledHandler'] = rejectionHandledHandler;
        }
    })()

    I'll note that zone.js has another bug that above doesn't try to fix, this time for the rejections handled event. This event should only be sent after a corresponding unhandled rejection event but zone.js sometimes sends it earlier and without a corresponding unhandled rejection. To implement the spec requires to store the promises who have unhandled rejections and this needs to use weak reference (e.g. WeakMap or WeakSet) and storage should have a bound since there is no guarantee that promise rejection will ever get handled. I think I saw Chrome have 1000 limit for how many promises it stores.
    See 6.2 step from the spec that defines what to do in case of promise rejection handled.

  5. kavitshah8 commented on Aug 22, 2023

    @kavitshah8

    @ioancris We came to know the following from opentelemetry-context-zone-peer-dep.

    Please note that due to an issue with zone.js, the ZoneContextManager does not work with JS code targeting ES2017+. In order to use the ZoneContextManager, please transpile back to ES2015.

    image

    Hence, zone.js does not work as expected with async ... await.

  6. JeanMeche commented on Aug 22, 2023

    @JeanMeche
    Member

    Zone.js doesn't work with async/await. That's why angular uses a babel plugin to transform async/await into generator functions.

  7. added
    canonicalThis issue represents a canonical design issue in Angular.
    P3An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
    and removed
    canonicalThis issue represents a canonical design issue in Angular.
    on Sep 13, 2023
  8. modified the milestones: needsTriage, Backlog on Sep 13, 2023
  9. nicetomytyuk commented on Jun 4, 2024

    @nicetomytyuk

    Is there any update on it?

  10. RayhanulRumel commented on Jun 6, 2024

    @RayhanulRumel

    mediatmpIMG_5359
    I see these errors when I open my app using Safari on my iPhone. However, I don't get these errors by using a laptop with different browsers like Opera, Chrome, and Firefox. Is there any fix to it now?

  11. added 2 commits that reference this issue on Apr 11, 2026
    acf8233
    45a4650
  12. added a commit that references this issue on Apr 15, 2026
    eddca42
  13. added a commit that references this issue on Apr 15, 2026
    fc6a7ee
  14. angular-automatic-lock-bot commented on May 16, 2026

    @angular-automatic-lock-bot

    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.

  15. locked and limited conversation to collaborators on May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3An issue that is relevant to core functions, but does not impede progress. Important, but not urgentarea: zonesIssues related to zone.jsbug

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions