From 6910118b0d82255be60a8580957383d227bcd58e Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sun, 11 Apr 2021 14:58:00 +0800 Subject: [PATCH] test(zone.js): should invoke XHR task even onload handler throw error. (#41562) Close #41520. This case related to the issue #41522. ``` Zone.root .fork({ name: 'xhr', onHasTask(delegate, currentZone, zone, taskState) { console.log('hasMacrotask', taskState.macroTask); return delegate.hasTask(zone, taskState); }, }) .run(() => { const xhr = new XMLHttpRequest(); xhr.open('GET', 'https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.11.4/zone.min.js'); xhr.addEventListener('load', () => { throw new Error(); }); xhr.send(); }); ``` zone.js invoke all `onload` event handlers before change the XHR task's state from `scheduled` to `notscheduled`, so if any `onload` listener throw error, the XHR task wlll be hang to `scheduled`, and leave the macroTask status in the zone wrongly. This has been fixed in the previous commit, this commit add test to verify the case. PR Close #41562 --- .../test/browser/XMLHttpRequest.spec.ts | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/packages/zone.js/test/browser/XMLHttpRequest.spec.ts b/packages/zone.js/test/browser/XMLHttpRequest.spec.ts index da23b072ff..37fb6f8427 100644 --- a/packages/zone.js/test/browser/XMLHttpRequest.spec.ts +++ b/packages/zone.js/test/browser/XMLHttpRequest.spec.ts @@ -103,6 +103,77 @@ describe('XMLHttpRequest', function() { req!.send(); }); + it('should run onload listeners before internal readystatechange', function(done) { + const logs: string[] = []; + const xhrZone = Zone.current.fork({ + name: 'xhr', + onInvokeTask: (delegate, curr, target, task, applyThis, applyArgs) => { + logs.push('invokeTask ' + task.source); + return delegate.invokeTask(target, task, applyThis, applyArgs); + } + }); + + xhrZone.run(function() { + const req = new XMLHttpRequest(); + req.onload = function() { + logs.push('onload'); + (window as any)[Zone.__symbol__('setTimeout')](() => { + expect(logs).toEqual([ + 'invokeTask XMLHttpRequest.addEventListener:load', 'onload', + 'invokeTask XMLHttpRequest.send' + ]) + done(); + }); + }; + req.open('get', '/', true); + req.send(); + }); + }); + + it('should invoke xhr task even onload listener throw error', function(done) { + const logs: string[] = []; + const xhrZone = Zone.current.fork({ + name: 'xhr', + onInvokeTask: (delegate, curr, target, task, applyThis, applyArgs) => { + logs.push('invokeTask ' + task.source); + return delegate.invokeTask(target, task, applyThis, applyArgs); + }, + onHasTask: (delegate, curr, target, hasTaskState) => { + if (hasTaskState.change === 'macroTask') { + logs.push('hasTask ' + hasTaskState.macroTask); + } + return delegate.hasTask(target, hasTaskState); + } + }); + + xhrZone.run(function() { + const req = new XMLHttpRequest(); + req.onload = function() { + logs.push('onload'); + throw new Error('test'); + }; + const unhandledRejection = (e: PromiseRejectionEvent) => { + logs.push(e.reason.message); + }; + window.addEventListener('unhandledrejection', unhandledRejection); + req.addEventListener('load', () => { + logs.push('onload1'); + (window as any)[Zone.__symbol__('setTimeout')](() => { + expect(logs).toEqual([ + 'hasTask true', 'invokeTask XMLHttpRequest.addEventListener:load', 'onload', + 'invokeTask XMLHttpRequest.addEventListener:load', 'onload1', + 'invokeTask XMLHttpRequest.send', 'hasTask false', + 'invokeTask Window.addEventListener:unhandledrejection', 'test' + ]); + window.removeEventListener('unhandledrejection', unhandledRejection); + done(); + }); + }); + req.open('get', '/', true); + req.send(); + }); + }); + it('should return null when access ontimeout first time without error', function() { let req: XMLHttpRequest = new XMLHttpRequest(); expect(req.ontimeout).toBe(null);