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

Thrown error no longer propagates (caught in the try-catch inside invokeTask in patchEventTarget in events.ts) #41867

Closed
br-star opened this issue Apr 28, 2021 · 7 comments
Assignees
Labels
area: zones P2 The issue is important to a large percentage of users, with a workaround regression Indicates than the issue relates to something that worked in a previous version state: confirmed state: has PR
Milestone

Comments

@br-star
Copy link

br-star commented Apr 28, 2021

I have an application that performs validation on mouse events. If validation fails, it throws an Error. We catch thrown errors in our application by doing:
window.addEventListener('error', (event) => { handleUncaughtError(event, logger); });

This worked fine: task.invoke() in the below code would call into our code, which would throw an error and would be caught by the above listener.

var invokeTask = function (task, target, event) {
        // for better performance, check isRemoved which is set
        // by removeEventListener
        if (task.isRemoved) {
            return;
        }
        var delegate = task.callback;
        if (typeof delegate === 'object' && delegate.handleEvent) {
            // create the bind version of handleEvent when invoke
            task.callback = function (event) { return delegate.handleEvent(event); };
            task.originalDelegate = delegate;
        }
        // invoke static task.invoke
        task.invoke(task, target, [event]);
        var options = task.options;
        if (options && typeof options === 'object' && options.once) {
            // if options.once is true, after invoke once remove listener here
            // only browser need to do this, nodejs eventEmitter will cal removeListener
            // inside EventEmitter.once
            var delegate_1 = task.originalDelegate ? task.originalDelegate : task.callback;
            target[REMOVE_EVENT_LISTENER].call(target, event.type, delegate_1, options);
        }
    };

However, now the callsite for task.invoke() looks like:

var invokeTask = function (task, target, event) {
        // for better performance, check isRemoved which is set
        // by removeEventListener
        if (task.isRemoved) {
            return;
        }
        var delegate = task.callback;
        if (typeof delegate === 'object' && delegate.handleEvent) {
            // create the bind version of handleEvent when invoke
            task.callback = function (event) { return delegate.handleEvent(event); };
            task.originalDelegate = delegate;
        }
        // invoke static task.invoke
        // need to try/catch error here, otherwise, the error in one event listener
        // will break the executions of the other event listeners. Also error will
        // not remove the event listener when `once` options is true.
        var error;
        try {
            task.invoke(task, target, [event]);
        }
        catch (err) {
            error = err;
        }
        var options = task.options;
        if (options && typeof options === 'object' && options.once) {
            // if options.once is true, after invoke once remove listener here
            // only browser need to do this, nodejs eventEmitter will cal removeListener
            // inside EventEmitter.once
            var delegate_1 = task.originalDelegate ? task.originalDelegate : task.callback;
            target[REMOVE_EVENT_LISTENER].call(target, event.type, delegate_1, options);
        }
        return error;
    };

and it seems like the error is ignored in globalCallback()

function globalCallback(context, event, isCapture) {
        // https://github.com/angular/zone.js/issues/911, in IE, sometimes
        // event will be undefined, so we need to use window.event
        event = event || _global.event;
        if (!event) {
            return;
        }
        // event.target is needed for Samsung TV and SourceBuffer
        // || global is needed https://github.com/angular/zone.js/issues/190
        var target = context || event.target || _global;
        var tasks = target[exports.zoneSymbolEventNames[event.type][isCapture ? utils_1.TRUE_STR : utils_1.FALSE_STR]];
        if (tasks) {
            // invoke all tasks which attached to current target with given event.type and capture = false
            // for performance concern, if task.length === 1, just invoke
            if (tasks.length === 1) {
                invokeTask(tasks[0], target, event);
            }
            else {
                // https://github.com/angular/zone.js/issues/836
                // copy the tasks array before invoke, to avoid
                // the callback will remove itself or other listener
                var copyTasks = tasks.slice();
                var errors = [];
                for (var i = 0; i < copyTasks.length; i++) {
                    if (event && event[IMMEDIATE_PROPAGATION_SYMBOL] === true) {
                        break;
                    }
                    var err = invokeTask(copyTasks[i], target, event);
                    err && errors.push(err);
                }
                var _loop_1 = function (i) {
                    var err = errors[i];
                    api.nativeScheduleMicroTask(function () {
                        throw err;
                    });
                };
                for (var i = 0; i < errors.length; i++) {
                    _loop_1(i);
                }
            }
        }
    }

IIUC, this is caused by the fix for this #41562 (comment)

This is a regression (I tested with a version from 3 weeks ago and it worked as expected).

Can we ensure that all caught errors are rethrown? Or whatever is the Angular way to undo this regression.

@JiaLiPassion
Copy link
Contributor

@br-star , could you share are reproduce repo? The error should be rethrown by the following code.

api.nativeScheduleMicroTask(function () {
    throw err;
});

And you can catch it by using

window.addEventListener('unhandledrejection', function (event) {
}

@br-star
Copy link
Author

br-star commented Apr 28, 2021

In my callstack, it goes through

if (tasks.length === 1) {
    invokeTask(tasks[0], target, event);
}

@JiaLiPassion
Copy link
Contributor

@br-star , in that case, the behavior doesn't change by this #41562 (comment) commit, the error will be re-thrown, and it will be caught by the global error handler of Angular, so you will not catch this error by window error event listener even before this commit.

@br-star
Copy link
Author

br-star commented Apr 29, 2021

I'm a little confused, where is it re-throwing? invokeTask catches the error, doesn't store it anywhere, returns it, and

if (tasks.length === 1) {
    invokeTask(tasks[0], target, event);
}

doesn't read the return value?

@JiaLiPassion
Copy link
Contributor

On you are right, the code is not handled right here since the recent refactor, I will fix it. Thanks.

@br-star
Copy link
Author

br-star commented Apr 29, 2021

Thanks!

JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Apr 29, 2021
Close angular#41867

In the previous commit angular#41562 (comment),
the error thrown in the event listener will be caught and re-thrown, but there is a bug
in the commit, if there is only one listener for the specified event name, the error
will not be re-thrown, so this commit fixes the issue and make sure the error is re-thrown.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Apr 29, 2021
Close angular#41867

In the previous commit angular#41562 (comment),
the error thrown in the event listener will be caught and re-thrown, but there is a bug
in the commit, if there is only one listener for the specified event name, the error
will not be re-thrown, so this commit fixes the issue and make sure the error is re-thrown.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Apr 29, 2021
Close angular#41867

In the previous commit angular#41562 (comment),
the error thrown in the event listener will be caught and re-thrown, but there is a bug
in the commit, if there is only one listener for the specified event name, the error
will not be re-thrown, so this commit fixes the issue and make sure the error is re-thrown.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Apr 29, 2021
Close angular#41867

In the previous commit angular#41562 (comment),
the error thrown in the event listener will be caught and re-thrown, but there is a bug
in the commit, if there is only one listener for the specified event name, the error
will not be re-thrown, so this commit fixes the issue and make sure the error is re-thrown.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Apr 30, 2021
Close angular#41867

In the previous commit angular#41562 (comment),
the error thrown in the event listener will be caught and re-thrown, but there is a bug
in the commit, if there is only one listener for the specified event name, the error
will not be re-thrown, so this commit fixes the issue and make sure the error is re-thrown.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Apr 30, 2021
Close angular#41867

In the previous commit angular#41562 (comment),
the error thrown in the event listener will be caught and re-thrown, but there is a bug
in the commit, if there is only one listener for the specified event name, the error
will not be re-thrown, so this commit fixes the issue and make sure the error is re-thrown.
@jessicajaniuk jessicajaniuk added the P2 The issue is important to a large percentage of users, with a workaround label Apr 30, 2021
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Apr 30, 2021
@AndrewKushnir AndrewKushnir added the regression Indicates than the issue relates to something that worked in a previous version label May 3, 2021
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Jun 2, 2021
Close angular#41867

In the previous commit angular#41562 (comment),
the error thrown in the event listener will be caught and re-thrown, but there is a bug
in the commit, if there is only one listener for the specified event name, the error
will not be re-thrown, so this commit fixes the issue and make sure the error is re-thrown.
JiaLiPassion added a commit to JiaLiPassion/angular that referenced this issue Jun 2, 2021
Close angular#41867

In the previous commit angular#41562 (comment),
the error thrown in the event listener will be caught and re-thrown, but there is a bug
in the commit, if there is only one listener for the specified event name, the error
will not be re-thrown, so this commit fixes the issue and make sure the error is re-thrown.
AndrewKushnir pushed a commit to JiaLiPassion/angular that referenced this issue Jun 9, 2021
Close angular#41867

In the previous commit angular#41562 (comment),
the error thrown in the event listener will be caught and re-thrown, but there is a bug
in the commit, if there is only one listener for the specified event name, the error
will not be re-thrown, so this commit fixes the issue and make sure the error is re-thrown.
alxhub pushed a commit that referenced this issue Jun 10, 2021
…ly (#41868)

Close #41867

In the previous commit #41562 (comment),
the error thrown in the event listener will be caught and re-thrown, but there is a bug
in the commit, if there is only one listener for the specified event name, the error
will not be re-thrown, so this commit fixes the issue and make sure the error is re-thrown.

PR Close #41868
@alxhub alxhub closed this as completed in 299f92c Jun 10, 2021
@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 Jul 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: zones P2 The issue is important to a large percentage of users, with a workaround regression Indicates than the issue relates to something that worked in a previous version state: confirmed state: has PR
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants